@@ -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!
0 commit comments