Skip to content

Commit 616fa28

Browse files
committed
docs: update README with file handling details and examples
1 parent 85e19c5 commit 616fa28

File tree

4 files changed

+114
-6
lines changed

4 files changed

+114
-6
lines changed

README.md

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@ This plugin is developed and tested against Payload v3.
2525
custom adapters such as S3, GCS, [plugin-cloud-storage](https://www.npmjs.com/package/@payloadcms/plugin-cloud-storage) etc.
2626
- The main purpose of this is to improve the file system performance when you have
2727
a large number of files.
28-
- I assume cloud storage providers don't have the same performance issues and
28+
- I assume cloud storage providers don't have the same performance issues and
2929
organizing files in a flat structure is not a problem.
3030
- The added endpoint relies on the existing built-in static file handler.
3131
It only injects a wildcard URL pattern, modifies the `req`, and passes back to the original
3232
handler. It may break if Payload changes the internal static file handling logic in the future.
33+
- The modified `filenames` are not sanitized, and are not checked for existing overwrites.
34+
It will always overwrite existing files with the same path.
35+
- It is not possible to modify the filename but serving the file from a different url
36+
(i.e. the `filename` and the url path must match.)
37+
Since this is what the built-in static handler expects, and the file deletion logic
38+
also relies on the `filename` field to locate the file on disk.
3339

3440
# Installation
3541

@@ -73,8 +79,8 @@ export default buildConfig({
7379
media: {
7480
// Optional: defaults to true
7581
addFilePathEndpoint: true,
76-
// Optional: defaults to not modify paths
77-
generateFilePath: async ({ data, file, payloadUploadSizes }) => {
82+
// Optional: omit to keep the original filenames
83+
generateFilePath: ({ data, file, payloadUploadSizes }) => {
7884
// modify the main file path
7985
data.filename = '...';
8086
if (data.sizes) {
@@ -90,3 +96,105 @@ export default buildConfig({
9096
],
9197
});
9298
```
99+
100+
# Examples
101+
102+
### Place files in date sub folders
103+
104+
```typescript
105+
function generateFilePath({ data }) {
106+
const dir = new Date().toISOString().split('T')[0].replace(/-/g, '/'); // e.g. 2023/10/05
107+
data.filename = `${dir}/${data.filename}`;
108+
if (data.sizes) {
109+
for (const v of Object.values(data.sizes)) {
110+
v.filename = `${dir}/${v.filename}`;
111+
}
112+
}
113+
return data;
114+
}
115+
```
116+
117+
Will produce:
118+
119+
```
120+
media/2025/
121+
media/2025/10/
122+
media/2025/10/05/
123+
media/2025/10/05/foo.jpg
124+
media/2025/10/05/foo-100x100.jpg
125+
```
126+
127+
### Hash paths to avoid too many files in a single directory
128+
129+
```typescript
130+
import { createHash } from 'crypto';
131+
132+
function generateFilePath({ data, file }) {
133+
// use the main file's content to generate the hash
134+
const hash = createHash('sha256').update(file.data).digest('hex');
135+
const dir = `${hash.slice(0, 2)}/${hash.slice(2, 4)}`;
136+
data.filename = `${dir}/${data.filename}`;
137+
if (data.sizes) {
138+
for (const v of Object.values(data.sizes)) {
139+
v.filename = `${dir}/${v.filename}`;
140+
}
141+
}
142+
}
143+
```
144+
145+
Will produce:
146+
147+
```
148+
media/ab/
149+
media/ab/cd/
150+
media/ab/cd/foo.jpg
151+
media/ab/cd/foo-100x100.jpg
152+
```
153+
154+
### Append hash to filenames for cache busting
155+
156+
```typescript
157+
import { createHash } from 'crypto';
158+
import path from 'path';
159+
160+
function generateFilePath({ data, file, payloadUploadSizes }) {
161+
const hash = (buf: Uint8Array) => {
162+
return createHash('sha256').update(buf).digest('hex').slice(0, 4);
163+
};
164+
const mainFileHash = hash(file.data);
165+
const dir = mainFileHash.slice(0, 2) + '/' + mainFileHash.slice(2, 4);
166+
// append hash before the file extension
167+
const parsed = path.parse(data.filename!);
168+
data.filename = `${dir}/${parsed.name}.${mainFileHash}${parsed.ext}`;
169+
if (data.sizes) {
170+
for (const [k, v] of Object.entries(data.sizes)) {
171+
if (payloadUploadSizes?.[k]) {
172+
const parsed = path.parse(v.filename!);
173+
v.filename = `${dir}/${parsed.name}.${hash(payloadUploadSizes[k])}${parsed.ext}`;
174+
} else {
175+
v.filename = `${dir}/${v.filename}`;
176+
}
177+
}
178+
}
179+
return data;
180+
}
181+
```
182+
183+
Will produce:
184+
185+
```
186+
media/00/
187+
media/00/1a/
188+
media/00/1a/foo.001a.jpg
189+
media/00/1a/foo-100x100.9c4b.jpg
190+
media/48/
191+
media/48/fa/
192+
media/48/fa/bar.48fa.jpg
193+
media/48/fa/bar-100x100.67d0.jpg
194+
```
195+
196+
# Contributing
197+
198+
This plugin is developed for my personal projects and is in early development.
199+
I may not have the time to response promptly, but I welcome contributions and feedback.
200+
Feel free to open issues or submit pull requests. Any contributions are welcome!

dist/hooks.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/hooks.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hooks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export const getBeforeChangeHook = (
137137
const filePath = path.join(staticDir, file.filename);
138138
await fs.mkdir(path.dirname(filePath), { recursive: true });
139139
if (file.tempFilePath) {
140-
// if the file is already on disk (eg. large file upload), move it
140+
// if the file is already on disk (e.g. using temp directory), move it
141141
await fs.rename(file.tempFilePath, filePath);
142142
} else if (file.buffer) {
143143
// if the file is in memory, write it

0 commit comments

Comments
 (0)