workerd: "Exceeded the CPU time limit" on JSON parsing
Fixes workerd "Exceeded the CPU time limit" failures caused by parsing large JSON payloads inside a request handler. Use it when a worker crashes on JSON.parse of multi-megabyte bodies. Key trigger: the error appears only for large payloads while small payloads parse fine, and the free plan hits it sooner than paid.
TL;DR: Shrink the JSON before it reaches the worker, or stop parsing the whole body at once. Parse incrementally from a stream, accept smaller chunks from the client, or push the raw body to a Queue and parse it in a consumer that gets a fresh CPU budget per message. The CPU cap is per request and cannot be raised by config.
workerd: "Exceeded the CPU time limit" on JSON parsing- Confirm parsing is the hot spot. Wrap the parse call and log how long it takes:
const t0 = performance.now();
const data = JSON.parse(text);
console.log("parse_ms", performance.now() - t0, "bytes", text.length); Expected: the log shows parse time climbing with payload size, and large payloads coincide with the limit error in wrangler tail.
- Check which plan the worker runs on. The free plan allows far less CPU per request than paid, so the same payload can fail on free and pass on paid.
Expected: you know the plan and can predict which payload sizes are unsafe.
- If you control the sender, send smaller chunks. Break the payload into pieces client-side and POST them separately.
Expected: each request parses in well under the budget and the error stops.
- If the payload must stay large, parse it incrementally. Read the body as a stream and decode it in pieces instead of one JSON.parse call over the whole string.
Expected: no single blocking parse, CPU stays spread across the stream, tail shows no limit errors.
- For unavoidable bulk jobs, hand the raw body to a Queue and parse in the consumer:
await env.INGEST_QUEUE.send({ body: text });then do the JSON.parse inside the queue consumer handler. Expected: each message gets its own CPU budget, parsing completes, and the request handler returns fast.
- Verify with tail under the largest real payload.
Expected: parse logs stay under budget and no "Exceeded the CPU time limit" lines appear.
Use this when
- the worker throws "Exceeded the CPU time limit" exactly when handling large JSON bodies
- small payloads work and large ones crash, with the threshold depending on the plan
- an import, webhook, or migration endpoint receives multi-megabyte JSON
- JSON.parse (or a validation library over the parsed object) is the slowest step in the handler
Not for this skill when
- the error is "Script startup exceeded the CPU time limit" (that is top-level init, not request parsing)
- the limit trips on regex, crypto, or image work rather than JSON parsing
- you see memory errors instead of CPU errors (that is the 128 MB isolate limit)
- the payload is small but parsing still fails (look for malformed JSON or a poisoned object shape)
Variant phrasings
- Cloudflare Worker JSON.parse CPU limit exceeded
- "Exceeded the CPU time limit" JSON body worker
- worker crashes parsing large JSON payload
- JSON.parse too slow in Cloudflare Worker
- CPU time limit on webhook JSON parsing
Why it happens
workerd enforces a strict CPU time budget per request (tighter on the free plan). JSON.parse is fully CPU-bound and blocks the isolate, so a multi-megabyte body can burn the entire budget in one call. The cap is a platform guardrail against runaway isolates and cannot be raised in wrangler config, which is why the fix is always to parse less per request.
Edge cases
- Compression does not help. A gzipped 5 MB body still parses to the same object and costs the same CPU.
- Validation libraries (schema checks over the parsed object) add their own CPU on top of the parse. Budget for both.
- Streaming the response back does not reduce parse cost. The parse happens before you can stream anything.
- Queue consumers get their own budget per message, but a single message with a giant body can still trip the consumer's limit. Keep messages modest too.
- Upgrading from free to paid raises the budget but does not remove it. Payload growth will eventually catch up, so still chunk or stream.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_H8huvbyLL-xCiUwENNxp1Q
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.