payloadcms/payload · error · APIError

File data must be valid base64.

Error message

File data must be valid base64.

What it means

Thrown as a 400 inside `decodeBase64` when the input fails the format guard — the regex `^[a-z0-9+/]*={0,2}$` (case-insensitive) does not match, or the length modulo 4 is 1 (an impossible base64 length). This catches obvious non-base64 strings before any decoding attempt.

Source

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

        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)
    }
  }

  const data = Buffer.from(normalized, 'base64')

  if (data.toString('base64').replace(/=+$/, '') !== normalized.replace(/=+$/, '')) {
    throw new APIError('File data must be valid base64.', 400)
  }

  return data

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Strip any `data:...;base64,` prefix before passing the value
  2. Use standard base64 encoding (A–Z, a–z, 0–9, +, /) with 0–2 `=` padding characters
  3. For URL-safe base64, convert `-`→`+` and `_`→`/` and add padding before sending

Example fix

// before
data: 'data:image/png;base64,iVBORw0KGgo='
// after
const raw = 'iVBORw0KGgo='
new FileInput({ source: 'base64', name: 'x.png', mimeType: 'image/png', data: raw })
Defensive patterns

Strategy: validation

Validate before calling

// Reject obviously non-base64 input before calling the tool
function assertBase64(v: string) {
  const norm = v.replace(/\s/g, '')
  if (!/^[a-z0-9+/]*={0,2}$/i.test(norm) || norm.length % 4 === 1)
    throw new Error('value is not valid base64 — strip data: prefix and use standard alphabet')
}

Type guard

function looksLikeBase64(v: string): boolean {
  const n = v.replace(/\s/g, '')
  return /^[a-z0-9+/]*={0,2}$/i.test(n) && n.length % 4 !== 1
}

Try / catch

import { APIError } from 'payload'
try {
  await tool.call({ source: 'base64', data })
} catch (e) {
  if (e instanceof APIError && e.statusCode === 400 && /must be valid base64/.test(e.message)) {
    // re-encode the source bytes canonically and retry
  }
  throw e
}

Prevention

When it happens

Trigger: Passing a data-URL-prefixed string like `data:image/png;base64,...` as `source: 'base64'` `data`; passing raw binary or UTF-8 text; passing hex-encoded bytes; a string whose length % 4 === 1.

Common situations: Client reusing an `<img src="data:...">` value verbatim; forgetting to strip the base64 prefix; encoding with a non-standard alphabet (URL-safe base64 with `-`/`_`); truncation that leaves an invalid length.

Related errors


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