payloadcms/payload · error · APIError

File exceeds the ${maxFileSize} byte upload limit.

Error message

File exceeds the ${maxFileSize} byte upload limit.

What it means

Thrown as a 400 after a file is materialized (either via base64 decode or externalURL download) when its `file.size` exceeds the global `req.payload.config.upload.limits.fileSize` cap. This is the post-hoc size check — it runs on the final byte count, not on an estimate.

Source

Thrown at packages/plugin-mcp/src/mcp/builtin/collections/fileInput.ts:125

    }

    file = await getExternalFile({
      data: {
        filename: sanitizeFilename(input.name || getURLFilename(url)),
        url: input.url,
      } as FileData,
      req,
      uploadConfig: {
        ...uploadConfig,
        externalFileHeaderFilter: uploadConfig.externalFileHeaderFilter ?? (() => ({})),
      },
    })
    file.mimetype = file.mimetype?.split(';')[0] || 'application/octet-stream'
    file.size = file.data.length
  }

  if (maxFileSize !== undefined && Number.isFinite(maxFileSize) && file.size > maxFileSize) {
    throw new APIError(`File exceeds the ${maxFileSize} byte upload limit.`, 400)
  }

  return file
}

function decodeBase64({ maxFileSize, value }: { maxFileSize?: number; value: string }): Buffer {
  const normalized = value.replace(/\s/g, '')

  if (!/^[a-z0-9+/]*={0,2}$/i.test(normalized) || normalized.length % 4 === 1) {
    throw new APIError('File data must be valid base64.', 400)
  }

  if (maxFileSize !== undefined && Number.isFinite(maxFileSize)) {
    const paddingLength = normalized.endsWith('==') ? 2 : normalized.endsWith('=') ? 1 : 0
    const decodedSize = Math.floor((normalized.length * 3) / 4) - paddingLength

    if (decodedSize > maxFileSize) {
      throw new APIError(`File exceeds the ${maxFileSize} byte upload limit.`, 400)

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Raise `config.upload.limits.fileSize` to the intended maximum (in bytes)
  2. Pre-validate file size before calling the tool — for `externalURL`, do a HEAD request and inspect `content-length`
  3. Compress or resize the asset so it fits under the configured limit

Example fix

// before
upload: { limits: { fileSize: 1_000_000 } } // 1 MB
// after
upload: { limits: { fileSize: 10_000_000 } } // 10 MB
Defensive patterns

Strategy: validation

Validate before calling

// For externalURL: HEAD the resource and pre-check Content-Length
const head = await fetch(url, { method: 'HEAD' })
const len = Number(head.headers.get('content-length') ?? 0)
if (maxFileSize !== undefined && len > maxFileSize) throw new Error(`remote file too large: ${len}`)

Try / catch

import { APIError } from 'payload'
try {
  await tool.call({ source: 'externalURL', url })
} catch (e) {
  if (e instanceof APIError && e.statusCode === 400 && /exceeds the .* byte upload limit/.test(e.message)) {
    // tell the user the file is too large for the configured cap
  }
  throw e
}

Prevention

When it happens

Trigger: Downloading a remote file via `externalURL` that turns out larger than `config.upload.limits.fileSize`; a base64 payload that decoded to within the estimated limit (see error 368) but the actual `data.length` exceeds it; `maxFileSize` undefined/non-finite lets a huge file through to here only if the limit was added at a different layer.

Common situations: `config.upload.limits.fileSize` set lower than the assets users actually upload; remote URL that returns a larger body than its Content-Length header advertised; no client-side size check before the tool call.

Related errors


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