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

  1. Verify the Node process has write/delete permission on staticDir and the file (check uid:gid, chmod).
  2. For Docker, ensure the volume is mounted read-write with matching PUID/PGID.
  3. Retry the document delete after clearing any file lock (close editors/AV scans on Windows).
  4. 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

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


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/c584ea894e672a97. Report an issue: GitHub.