To prevent uploads from overwriting each other in our media folder, is there a way to generate a unique name for a file upon upload?
Example:
Uploadingunknown.jpg
(again) would be renamed to[random-hash].jpg
on the server
Would this be accomplished by means of a middleware or something I'm missing?
Aka: handlers
in the Upload Options described here: https://payloadcms.com/docs/upload/overview#enabling-uploads
Thank you for responses / feedback!
I found this thread because I want to sanitize filenames to not include spaces.
Since uploads always have a URL attached, should this maybe be a core functionality?
something like sanitizeFilename: true
?
Solved it like this if anyone cares:
import { CollectionBeforeOperationHook } from 'payload/types'
/**
* Sanitizes the filename for upload collections to not include spaces or special characters
*/
export const sanitizeFileName: CollectionBeforeOperationHook = async ({
args,
}) => {
const files = args.req?.files
if (
files !== undefined &&
files !== null &&
typeof files === 'object' &&
files.file !== undefined &&
files.file !== null &&
typeof files.file === 'object' &&
files.file.name !== undefined &&
files.file.name !== null &&
typeof files.file.name === 'string'
) {
files.file.name = files.file.name.replace(/[^a-z0-9.]/gi, '_').toLowerCase()
}
}
I've tried a beforeChange
-Hook to manipulate the filename in the document. This changed the document entry, but not the actual file name. Also, the resized image names were still based on the original name.
I now use this hook here to set a nanoid as the filename. Thank you so much!
Using this in Payload v3.0.0-beta.19
:
import type { CollectionBeforeOperationHook } from "payload/types";
import slugify from "slugify";
/**
* Sanitizes the filename for upload collections to not include spaces or special characters
*/
export const sanitizeFileName: CollectionBeforeOperationHook = async ({
req,
}) => {
const file = req.file;
if (typeof file?.name === "string") {
file.name = slugify(file.name, { strict: true });
}
}
Uploaded files having the same name as something already in storage will be renamed with a -n
suffix for uniqueness such that unknown.jpg
uploaded a 2nd time is saved as unknown-1.jpg
. This should be happening automatically for you. If that is not the case you can open a bug for this issue.
If you prefer to have a unique hash instead, you'd need to do a bit of work which I can go into some detail if you want.
Let me know what you think!
Adding to this, it looks like a beforeOperation
hook on the collection is also suitable for this. A quick and dirty example:
beforeOperation: [async ({ args }) => {
const files = args.req?.files;
if (files && files.file && files.file.name) {
const parts = files.file.name.split('.');
files.file.name = `${(Math.random() + 1).toString(36).substring(2)}.${parts[parts.length - 1]}`;
}
}]
It just replaces the filename in the request before anything is saved. Note that this does not at all guarantee that the filename is unique, although depending on usage collision are probably rare anyway and in case they do happen we can rely on the built-in functionality of adding the -n
suffix as Dan pointed out.
Using handlers
on the upload might be 'better', but I prefer using a hook to stay consistent with the rest of my application.
+1. This is probably what I'd do. Hell yeah, beforeOperation
! The use cases for that one are less than the beforeChange
, etc. but I always knew there were use cases out there!!!
fwiw stumbled on this discussion searching for a way to encode upload filenames that had spaces in them.
good for this purpose too right?
hooks: {
beforeOperation: [
async ({ args }) => {
const files = args.req?.files;
if (files && files.file && files.file.name) {
files.file.name = encodeURIComponent(files.file.name);
}
}
]
},
Anything from suggestions works for me, so I needed to debug it and this works for me.
Version 3.0.0-beta.58
import { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: true,
fields: [
{
name: 'text',
type: 'text',
},
],
hooks: {
beforeOperation: [
async (req) => {
if (req.operation === 'create') {
if (req.req.file)
req.req.file.name = req.req.file?.name.replace(/[^a-z0-9.]/gi, '').toLowerCase()
}
},
],
},
}
Star
Discord
online
Get help straight from the Payload team with an Enterprise License.