Budibase/budibase · error
Invalid JSON body
Error message
Invalid JSON body
What it means
makeApiCall in frontend-core serializes the request body with JSON.stringify when the call is marked as JSON. If serialization throws — typically because the body contains a circular reference or BigInt — it throws an APIError with message 'Invalid JSON body', status 400, plus the url and method. This is a client-side pre-flight failure; no request is ever sent to the server.
Source
Thrown at packages/frontend-core/src/api/index.ts:181
let headers: Headers = { Accept: "application/json" }
headers[Header.SESSION_ID] = APISessionID
if (!external) {
headers[Header.API_VER] = ApiVersion
}
if (json) {
headers["Content-Type"] = "application/json"
}
if (config?.attachHeaders) {
config.attachHeaders(headers, { url, method })
}
// Build request body
let requestBody: any = body
if (json) {
try {
requestBody = JSON.stringify(body)
} catch (error) {
throw makeError("Invalid JSON body", url, method)
}
}
// Make request
let response: Response
try {
response = await fetch(url, {
method,
headers,
body: requestBody,
credentials: "same-origin",
signal,
})
} catch (error) {
delete cache[url]
if (signal?.aborted) {
throw error
}View on GitHub (pinned to a81a902e9a)
Solutions
- Inspect the body for circular references and send only plain serializable data.
- Convert BigInt values to strings or numbers before the call.
- Strip non-serializable fields (functions, DOM nodes, class instances) by mapping to a plain object.
- As a debug aid, try JSON.stringify(body) in a try/catch where the call is made to reproduce the failure.
Example fix
// before: circular reference
api.post('/rows', { table, parent: row, row }) // row.parent === table
// after: send plain data
api.post('/rows', { tableId: table._id, name: row.name }) Defensive patterns
Strategy: type-guard
Validate before calling
function isSerializable(v: unknown): boolean {
try { JSON.stringify(v); return true } catch { return false }
}
if (!isSerializable(body)) throw new Error("Body is not JSON-serializable") Type guard
const isPlainBody = (b: unknown): b is Record<string, unknown> => typeof b === "object" && b !== null && !Array.isArray(b) === false || (typeof b === "object" && b !== null && Object.getPrototypeOf(b) === Object.prototype)
Try / catch
try {
await api.post(url, body)
} catch (e) {
if (e?.message === "Invalid JSON body") {
console.error("Request body for", e.url, e.method, "is not serializable")
}
} Prevention
- Send plain objects/arrays only — strip class instances and stores
- Convert BigInt values to strings before sending
- Never reuse request/response objects that hold circular references
- Test bodies with JSON.stringify in dev when payloads are dynamic
When it happens
Trigger: Calling any frontend-core API wrapper with json:true (non-GET) where body contains circular object references, BigInt values, or functions that JSON.stringify cannot serialize.
Common situations: Passing a Svelte store object or a component/props graph with back-references as the body; including BigInt ids from some ORMs; accidentally passing FormData into a JSON call.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Failed to send request
- OIDC Config contents invalid
- Error constructing OIDC authentication configuration - ${err
- Cannot render an empty flow chain
- Exa error: ${response.status} ${response.statusText} - ${err
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/53d521c72db5bc45.
Report an issue: GitHub.