Cloudflare Workers Request Body Already Used: Clone Before Reading Twice
When a Cloudflare Worker throws a body-used TypeError, the request body has already been consumed. A Request body is a one-use stream: calling request.json(), request.formData(), or request.text() reads it, so a later consumer cannot read the same body again.
The direct fix is to parse once and pass the parsed value to the rest of the handler. If two independent consumers genuinely need the body, call request.clone() before the first read and give each consumer its own clone. For large uploads, avoid making multiple in-memory copies and use the Streams API instead.
Find the first body reader
Search the handler and any middleware for every body-reading method:
await request.json();await request.text();await request.formData();await request.arrayBuffer();The first call consumes the body. This common pattern fails because the second call sees a used stream:
export default { async fetch(request) { const payload = await request.json(); const raw = await request.text(); // The body was already consumed.
return Response.json({ payload, raw }); },};The Cloudflare Workers POST example calls out the same boundary: after formData() has read the body, another access to request.body can throw a TypeError. The error may appear in a framework middleware rather than next to the line that first consumed the request, so trace the request through the whole pipeline.
request.bodyUsed can confirm the state:
console.log({ bodyUsed: request.bodyUsed });Use that property for diagnosis, not as a repair. Once it is true, checking it does not rewind the stream.
Prefer one parse and explicit data flow
For JSON, parse the request once and pass the result to validation, authorization, logging, and the application code:
export default { async fetch(request) { if (request.method !== 'POST') { return new Response('Method Not Allowed', { status: 405 }); }
const payload = await request.json(); const isValid = payload && typeof payload === 'object';
if (!isValid) { return Response.json({ error: 'Invalid JSON' }, { status: 400 }); }
const result = await savePayload(payload); return Response.json({ ok: true, result }); },};Replace savePayload with your application function. The important part is that it receives payload; it does not receive the original Request and try to parse it again.
This approach is also easier to audit. A logger that needs a safe subset can record selected fields from the parsed object instead of storing a second copy of an unbounded raw body.
Clone before the first read when two consumers are required
Sometimes a request must be parsed by two independent paths, such as a form parser and a raw-body signature check. Create the clones before either branch consumes the original:
export default { async fetch(request) { const formRequest = request.clone(); const signatureRequest = request.clone();
const form = await formRequest.formData(); const rawBody = await signatureRequest.text(); const signature = request.headers.get('x-signature');
const valid = await verifySignature(rawBody, signature); if (!valid) { return new Response('Invalid signature', { status: 401 }); }
return Response.json({ message: form.get('message'), }); },};The clones are independent body streams, but they still represent the same incoming bytes. Verify the raw body before using parsed fields for a security-sensitive operation, and use the signing service’s exact canonicalization rules.
Request.clone() is not a way to make an unlimited number of cheap copies. The MDN Request.clone() reference explains that unread data can be queued for the slower consumer. A large or unevenly consumed body can therefore increase memory pressure.
Use streams for large bodies
If the request contains a large file or a long JSON payload, cloning and buffering it for multiple readers can be the wrong design. Cloudflare documents the Streams API for processing request and response bodies incrementally within the Worker’s memory boundary.
For a pass-through endpoint, keep the body as a stream instead of calling a convenience parser:
export default { async fetch(request) { const upstream = new Request('https://api.example.com/upload', { method: request.method, headers: request.headers, body: request.body, });
return fetch(upstream); },};The exact forwarding headers and authentication depend on your upstream. Do not log the stream or read it for diagnostics before forwarding it. If you need both inspection and forwarding, design a bounded streaming transform or accept that the body must be buffered under a limit you can enforce.
Cloudflare’s Streams API documentation notes that streaming avoids buffering an entire request and can help with large bodies. This is a memory and data-flow decision, not a workaround for a request-size limit. The existing Cloudflare Workers 413 guide covers the separate problem of an upload being rejected before application code can process it.
Check framework middleware and response bodies
The same one-read rule applies when a framework wraps the Workers Request. A middleware may call request.json() to validate a payload, while the route handler calls request.formData() again. Decide which layer owns parsing and pass the result through the framework’s context when possible.
Response bodies follow the same Fetch API model. If code reads a Response and later needs to return or inspect it again, clone the response before the first read:
const upstreamResponse = await fetch(url);const responseForAudit = upstreamResponse.clone();const responseForClient = upstreamResponse;
const statusText = await responseForAudit.text();return responseForClient;Do not add clones reflexively. First map the consumers, then choose a single parse, a bounded clone, or a stream. Cloudflare’s Request runtime reference documents body, bodyUsed, and the body-reading methods available in Workers.
Verify the fix at the same boundary
Test the request path with the content types and sizes your production handler accepts:
- Send a small JSON body and confirm the parser and business logic both see the value.
- Send a form body through every middleware layer.
- Test an invalid signature without logging the raw secret-bearing body.
- Test a larger body through the streaming or size-limit path.
- Inspect logs for a second body reader rather than adding a second clone automatically.
If bodyUsed is already true when the route starts, the fix belongs in middleware or the adapter boundary. If the body is read only once but the request still fails with 413, switch to the size-limit diagnosis instead of changing clone logic.
The practical rule is to make body ownership explicit. Parse once when possible, clone before the first read only when two consumers need independent streams, and stream large payloads instead of multiplying buffered copies.
FAQ
Q: Why does Cloudflare Workers say the request body was already used?
A: A previous call such as request.json(), request.formData(), or request.text() consumed the one-use body stream. Find the first reader and either pass its result onward or clone the request before that first read.
Q: Can I call request.clone() after request.json()?
A: No. Clone before the body is consumed. A clone made after the first read cannot restore the original stream.
Q: Should I clone a request for every middleware?
A: Usually no. Prefer one parsing layer and pass structured data through the request context. Use a bounded number of clones only when independent consumers truly need the raw or parsed body.
Q: Does cloning bypass Cloudflare’s request body limit?
A: No. Cloning only creates another readable body stream. A request can still be rejected by the platform’s size limit, which is a different failure path from a body-used error.
References:
Cloudflare Workers: Request runtime API
Report a typo or broken link, or suggest a related topic.