denoland/deno · error · TypeError

Input request's body is unusable

Error message

Input request's body is unusable

What it means

Request constructor step 41: when init.body is absent but the input Request has a body, the input body is proxied into the new request — but only if it is still usable. input[_body].unusable() returns true once the stream is disturbed or locked (already read, tee'd, or piped), and then TypeError 'Input request's body is unusable' is thrown.

Source

Thrown at ext/fetch/23_request.js:585

    // 37.
    if (init.body !== undefined && init.body !== null) {
      const res = extractBody(init.body);
      initBody = res.body;
      if (res.contentType !== null && !this[_headers].has("content-type")) {
        this[_headers].append("Content-Type", res.contentType);
      }
    }

    // 38.
    const inputOrInitBody = initBody ?? inputBody;

    // 40.
    let finalBody = inputOrInitBody;

    // 41.
    if (initBody === null && inputBody !== null) {
      if (input[_body] && input[_body].unusable()) {
        throw new TypeError("Input request's body is unusable");
      }
      finalBody = inputBody.createProxy();
    }

    // 42.
    request.body = finalBody;
  }

  get method() {
    webidl.assertBranded(this, RequestPrototype);
    if (this[_method]) {
      return this[_method];
    }
    this[_method] = this[_request].method;
    return this[_method];
  }

  get url() {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Read the body once into memory and rebuild: new Request(url, { method, headers, body: await input.text() })
  2. Clone BEFORE reading: const copy = input.clone(); const text = await input.text(); — then reuse copy
  3. In middleware, buffer the body first and pass the buffer to downstream handlers

Example fix

// before
const bodyText = await input.text(); // input now unusable
const forwarded = new Request(target, input); // TypeError

// after
const bodyText = await input.text();
const forwarded = new Request(target, {
  method: input.method,
  headers: input.headers,
  body: bodyText,
});
Defensive patterns

Strategy: validation

Validate before calling

async function rewrapRequest(input, targetUrl) {
  // Buffer once; safe even if already partially handled upstream
  const bodyText = await input.clone().text(); // clone guards against double-read below
  return new Request(targetUrl, {
    method: input.method,
    headers: input.headers,
    body: ['GET', 'HEAD'].includes(input.method) ? undefined : bodyText,
  });
}

Try / catch

try {
  derived = new Request(target, input);
} catch (err) {
  if (err instanceof TypeError && err.message.includes('body is unusable')) {
    derived = new Request(target, { method: input.method, headers: input.headers, body: bufferedBody });
  } else throw err;
}

Prevention

When it happens

Trigger: const reqB = new Request(reqA) after reqA's body was already read (reqA.text(), reqA.json(), iterated, or piped through a stream).

Common situations: Middleware that inspects a body (await req.text()) and then constructs a derived Request for forwarding; logging proxies that consume and re-wrap requests; retry wrappers around an already-read request.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/addf82a81f562d19. Report an issue: GitHub.