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

  1. Inspect the body for circular references and send only plain serializable data.
  2. Convert BigInt values to strings or numbers before the call.
  3. Strip non-serializable fields (functions, DOM nodes, class instances) by mapping to a plain object.
  4. 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

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

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/53d521c72db5bc45. Report an issue: GitHub.