payloadcms/payload · error · APIError

Staged upload was not found.

Error message

Staged upload was not found.

What it means

Thrown by `getStagedFile` when reading the temp file from disk fails (the `fs.readFile` call rejects). The staged upload JWT is valid and the user/collection match, but the underlying file on disk is gone.

Source

Thrown at packages/payload/src/uploads/stagedUpload.ts:178

  ) {
    throw new APIError('Invalid staged upload.', 400)
  }

  const { uploadId } = uploadReference
  const upload = await verifyUploadID(req, uploadId)

  if (upload.collectionSlug !== collectionSlug || upload.user !== getUser(req)) {
    throw new Forbidden(req.t)
  }

  const directory = await getUploadDirectory(req, upload.collectionSlug)
  const tempFilePath = path.join(directory, upload.id)
  let data: Buffer

  try {
    data = await fs.readFile(tempFilePath)
  } catch {
    throw new APIError('Staged upload was not found.', 400)
  }

  if (data.length !== upload.filesize) {
    await fs.rm(tempFilePath, { force: true })
    throw new APIError('Staged upload is incomplete.', 400)
  }

  await fs.rm(tempFilePath, { force: true })

  return {
    name: upload.filename,
    data,
    mimetype: upload.mimeType,
    size: data.length,
  }
}

/**

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Treat an uploadId as single-use: stage -> PUT bytes -> consume once. Do not retry the document create with the same uploadId.
  2. If the operation failed transiently, re-stage the file from scratch (new instructions, re-upload bytes).
  3. Ensure persistent or sufficiently long-lived temp storage if your workflow may delay the document create beyond a few minutes.
  4. Check that `upload.tempFileDir` is not pointing at a container-local path that is lost on redeploy.

Example fix

// before -- retrying create with same uploadId after a transient failure
try { await createWithUpload(uploadId) } catch { await createWithUpload(uploadId) } // 2nd call: file gone

// after -- re-stage on failure
try {
  await createWithUpload(uploadId)
} catch {
  const instr = await generateStagedUploadInstructions({ ... })
  await putBytes(instr)
  await createWithUpload(instr.file.uploadReference.uploadId)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before consuming, ensure you are within the 1-hour window
const elapsed = Date.now() - stagedAt
if (elapsed > 55 * 60 * 1000) {
  // re-stage before it expires
}

Try / catch

try {
  await payload.create({ collection, data })
} catch (e) {
  if (e instanceof APIError && e.message === 'Staged upload was not found.') {
    // file is gone -- must re-stage from scratch
    const fresh = await generateStagedUploadInstructions({ ... })
    await putStagedBytes(fresh)
    await payload.create({ collection, data: { ...data, file: { uploadReference: { uploadId: fresh.file.uploadReference.uploadId } } } })
  } else throw e
}

Prevention

When it happens

Trigger: The temp file was already consumed and deleted by a prior `getStagedFile` call (each call deletes the file), or `removeExpiredUploads` cleaned it up after the one-hour window, or the temp directory was cleared by an external process / container restart.

Common situations: Calling create/update twice with the same uploadId (second call finds no file); the server restarted and temp files are on ephemeral storage; the one-hour JWT window expired and the cleanup sweeper removed the file; a deployment wiped the `tempFileDir`.

Related errors


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