payloadcms/payload · error · ErrorDeletingFile
There was an error deleting file.
Error message
There was an error deleting file.
What it means
ErrorDeletingFile thrown when fs.unlink of the primary uploaded file fails despite fileExists returning true. Thrown inside deleteAssociatedFiles, which runs when a document with an upload field is deleted or its file is replaced (overrideDelete or files.length > 0).
Source
Thrown at packages/payload/src/uploads/deleteAssociatedFiles.ts:40
doc,
files = [],
overrideDelete,
req,
}) => {
if (!collectionConfig.upload) {
return
}
if (overrideDelete || files.length > 0) {
const { staticDir: staticPath } = collectionConfig.upload
const fileToDelete = `${staticPath}/${doc.filename as string}`
try {
if (await fileExists(fileToDelete)) {
await fs.unlink(fileToDelete)
}
} catch (ignore) {
throw new ErrorDeletingFile(req.t)
}
if (doc.sizes) {
const sizes: FileData[] = Object.values(doc.sizes)
// Since forEach will not wait until unlink is finished it could
// happen that two operations will try to delete the same file.
// To avoid this it is recommended to use "sync" instead
for (const size of sizes) {
const sizeToDelete = `${staticPath}/${size.filename}`
try {
if (await fileExists(sizeToDelete)) {
await fs.unlink(sizeToDelete)
}
} catch (ignore) {
throw new ErrorDeletingFile(req.t)
}
}View on GitHub (pinned to 00c58b35c0)
Solutions
- Verify the Node process has write/delete permission on staticDir and the file (check uid:gid, chmod).
- For Docker, ensure the volume is mounted read-write with matching PUID/PGID.
- Retry the document delete after clearing any file lock (close editors/AV scans on Windows).
- If files are routinely removed out-of-band, consider a custom upload handler that tolerates missing files instead of throwing.
Example fix
// before — throws if file locked
await fs.unlink(fileToDelete)
// after — tolerate already-gone files, surface others
try {
await fs.unlink(fileToDelete)
} catch (e) {
if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e
} Defensive patterns
Strategy: try-catch
Validate before calling
import { access, constants } from 'fs/promises'
async function canDelete(p: string): Promise<boolean> {
try { await access(p, constants.W_OK); return true } catch { return false }
}
// before delete:
if (doc.filename && !(await canDelete(`${staticDir}/${doc.filename}`))) {
fixPermissionsOrWarn(staticDir)
} Type guard
const isDeletableError = (e: NodeJS.ErrnoException): boolean =>
['EACCES', 'EPERM', 'EBUSY'].includes(e.code ?? '') Try / catch
try {
await payload.delete({ collection: 'media', id })
} catch (e) {
if (e.name === 'ErrorDeletingFile') {
logger.error('Could not delete file on disk; document was deleted')
// optionally queue a cleanup job
} else throw e
} Prevention
- Run the Node process as the owner of staticDir.
- Set group-write + same gid for uploader/worker processes.
- Avoid antivirus/file-locking on Windows for the media dir.
- Periodically reconcile DB records with disk to detect orphans.
When it happens
Trigger: Deleting or updating a media document where the main file at `${staticDir}/${doc.filename}` exists but unlink rejects — EACCES (permissions), EBUSY/EPERM on Windows, stale NFS handle, or the file removed by another process between the exists check and unlink.
Common situations: staticDir written by a different OS user than the Node process (permission mismatch); Docker volume mounted read-only or with wrong uid:gid; NFS/SMB mount hiccup; antivirus/file-lock on Windows locking the file; manual cleanup script racing with the delete request.
Related errors
- Could not read uploaded file for validation.
- Invalid filename.
- There was a problem while uploading the file.
- Collection ${args.collection.config.slug} has disabled bulk
- Missing 'where' query of documents to delete.
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/c584ea894e672a97.
Report an issue: GitHub.