payloadcms/payload · error · APIError
Uploaded file is larger than expected.
Error message
Uploaded file is larger than expected.
What it means
The staged `PUT /upload-instructions/:uploadId` streams the request body to disk, accumulating `uploadedSize` per chunk. If at any point `uploadedSize` exceeds the `filesize` declared when the upload ID was minted (signed JWT containing `filesize`), Payload throws `APIError` HTTP 400 `Uploaded file is larger than expected.` The partial `.part` file is cleaned up in the surrounding `catch`.
Source
Thrown at packages/payload/src/uploads/stagedUpload.ts:92
const uploadPath = path.join(directory, upload.id)
const temporaryPath = `${uploadPath}.${randomUUID()}.part`
const file = await fs.open(temporaryPath, 'wx')
let uploadedSize = 0
try {
try {
const reader = req.body?.getReader()
while (reader) {
const { done, value } = await reader.read()
if (done) {
break
}
uploadedSize += value.byteLength
if (uploadedSize > upload.filesize) {
throw new APIError('Uploaded file is larger than expected.', 400)
}
let offset = 0
while (offset < value.byteLength) {
const { bytesWritten } = await file.write(value, offset)
offset += bytesWritten
}
}
if (uploadedSize !== upload.filesize) {
throw new APIError('Uploaded file size does not match the expected size.', 400)
}
} finally {
await file.close()
}
const collection = req.payload.collections[upload.collectionSlug]!.config
await checkFileRestrictions({View on GitHub (pinned to 00c58b35c0)
Solutions
- Request fresh upload instructions (`POST /upload-instructions`) with the exact byte length of the file you will stream, then PUT exactly that many bytes.
- Compute `filesize` from the actual `File`/`Blob`/buffer you upload (`file.size`), not a metadata estimate.
- Do not reuse an uploadId across different files or after the file changes.
- Ensure the HTTP client doesn't transform the body (extra framing, appended trailers).
- If resumable uploads are needed, re-mint the uploadId rather than appending to a used one.
Example fix
// before — filesize mismatch
const { file } = await getUploadInstructions({
collectionSlug: 'media', filename: 'a.png', filesize: 1024, mimeType: 'image/png', req, // wrong size
})
await fetch(file.request.url, { method: 'PUT', body: realBlob /* 2048 bytes */ })
// after — declare the true size
const realSize = realBlob.size
const { file } = await getUploadInstructions({
collectionSlug: 'media', filename: 'a.png', filesize: realSize, mimeType: 'image/png', req,
})
await fetch(file.request.url, { method: 'PUT', body: realBlob }) Defensive patterns
Strategy: validation
Validate before calling
const realSize = file.size // or Buffer.byteLength(buffer)
const { file: instr } = await getUploadInstructions({
collectionSlug: 'media', filename, filesize: Math.trunc(realSize), mimeType, req,
})
if (realSize !== Math.trunc(realSize)) {
throw new Error('filesize must be a non-negative safe integer matching the stream length')
} Type guard
function sizesMatch(declared: number, actual: number): boolean {
return Number.isSafeInteger(declared) && declared >= 0 && actual <= declared
}
if (!sizesMatch(instr.size, blob.size)) {
// re-request instructions with the correct size
} Try / catch
try {
const res = await fetch(instr.request.url, { method: 'PUT', body: blob })
if (res.status === 400) {
const { message } = await res.json().catch(() => ({}))
if (/larger than expected/i.test(message ?? '')) {
// re-mint instructions with blob.size and PUT again
}
}
} catch (err) { /* network */ } Prevention
- Derive `filesize` from the exact bytes you will stream (`file.size`).
- Re-request instructions whenever the file changes; never reuse an uploadId across files.
- Avoid middleware that mutates the request body length.
When it happens
Trigger: `PUT /api/upload-instructions/:uploadId` where the total streamed byte count surpasses the `filesize` baked into the signed uploadId token. The client sent more bytes than it declared in the prior `POST /upload-instructions`.
Common situations: The client computed `filesize` from a different (smaller) artifact than the one it actually streams (e.g. pre- vs post-compression, or a different file). A retry/resume appended to an existing stream. A proxy or middleware added bytes/chunked encoding unexpectedly. The client reused a stale uploadId for a larger file. Concurrency: two PUTs to the same id.
Related errors
- Upload collection ${upload.collectionSlug} was not found
- Exceeded file size limit. Limit: ${bytesToMB(filesizeLimit).
- Uploaded file size does not match the expected size.
- Invalid staged upload.
- Field ${field.label} has invalid relationship '${relationshi
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/7124c4502de4a0ea.
Report an issue: GitHub.