payloadcms/payload · error · APIError
Staged upload is incomplete.
Error message
Staged upload is incomplete.
What it means
Thrown by `getStagedFile` when the temp file exists on disk but its byte length does not equal `upload.filesize`. This means the PUT that was supposed to deliver the bytes either did not complete or wrote a partial file.
Source
Thrown at packages/payload/src/uploads/stagedUpload.ts:183
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,
}
}
/**
* Staged uploads hold a file temporarily until a document create or update uses it. The signed
* upload ID remembers which file and user the upload belongs to.
*/
const tokenExpiration = 60 * 60
View on GitHub (pinned to 00c58b35c0)
Solutions
- Re-stage the file: generate new upload instructions, re-PUT the full byte stream, then consume.
- Check client-side for upload aborts -- increase timeouts and add retry on the PUT step specifically.
- Verify no intermediary truncates the body (nginx proxy_read_timeout, CDN body limits).
- Ensure the temp directory has enough disk space for the full file.
Example fix
// before
const instr = await generateStagedUploadInstructions({ filesize: file.size, ... })
await fetch(instr.request.url, { method: 'PUT', body: streamThatMayAbort })
await createWithUpload(instr) // partial file on disk -> incomplete
// after
const instr = await generateStagedUploadInstructions({ filesize: file.size, ... })
const res = await fetch(instr.request.url, { method: 'PUT', body: fullBuffer })
if (!res.ok) throw new Error('PUT failed, re-stage before retrying')
await createWithUpload(instr) Defensive patterns
Strategy: try-catch
Validate before calling
// After PUT, verify the response is 204 before consuming
const res = await fetch(uploadUrl, { method: 'PUT', body: buf })
if (res.status !== 204) {
throw new Error('PUT did not complete successfully -- do not consume')
} Try / catch
try {
await payload.create({ collection, data })
} catch (e) {
if (e instanceof APIError && e.message === 'Staged upload is incomplete.') {
// re-stage and re-upload the full file
} else throw e
} Prevention
- Verify the PUT returns 204 before proceeding to create/update.
- Increase client and proxy timeouts for large uploads.
- Ensure sufficient disk space on the temp directory.
When it happens
Trigger: The PUT to `/upload-instructions/:uploadId` was interrupted mid-stream (client disconnect, network drop) so the `.part` file was renamed or a prior partial write persisted; or a crash happened between writing and the final size check.
Common situations: Client disconnected or timed out during the chunked PUT; a proxy closed the connection early; the server crashed mid-upload and on restart the temp file is stale; clock or filesystem corruption caused a truncated write.
Related errors
- Upload collection ${upload.collectionSlug} was not found
- You are not allowed to perform this action.
- Uploaded file is larger than expected.
- Uploaded file size does not match the expected size.
- Invalid staged upload.
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/81c3c0f51977bbef.
Report an issue: GitHub.