← textStream() for response/request/blob

v151 · fetch · member reference

Request.textStream()

Reads a Request body — an upload payload — as a ReadableStream of UTF-8-decoded strings. Same Body-mixin contract as Response.textStream(), applied to the request side before the request is sent.

Syntax

// WHATWG Fetch — Body mixin (normative IDL)
ReadableStream textStream();

// Call form
const stream = request.textStream();

Chromium's binding: [RuntimeEnabled=TextStreamMethod, CallWith=ScriptState, RaisesException] ReadableStream textStream(); in body.idl (the same mixin binding Request includes).

Source: Fetch Standard — textStream(); Chromium body.idl

Inputs

The method takes no parameters. Its only input is the receiver's own state: the Request object's body (null or a body with a ReadableStream) and whether that body is already disturbed or locked. There is no encoding argument — decoding is always UTF-8.

Source: Fetch Standard — textStream() method steps

Outputs

Returns a ReadableStream whose chunks are strings — the request body's bytes run through a TextDecoderStream set up with UTF-8, regardless of any Content-Type header or charset parameter on the request.

Null body: for a bodyless request (a plain GET, for example), the method returns a new, already-closed empty ReadableStream — not an error. Null-body calls are not single-use: every call creates a distinct closed stream object, and bodyUsed remains false throughout, because the unusable/disturbed contract only exists for non-null bodies.

Source: Fetch Standard — textStream() method steps; Encoding Standard — TextDecoderStream

Errors

TypeError — unusable receiver: if the request is unusable — its body is non-null and already disturbed or locked — the method throws a TypeError synchronously. This is the only exception in the method steps, mirrored by the binding's RaisesException.

Malformed bytes are not exceptions: the decoder runs in the default non-fatal error mode (TextDecoderOptions.fatal defaults to false), so invalid UTF-8 sequences appear in the output as U+FFFD replacement characters instead of throwing.

Source: Fetch Standard — textStream() method steps + unusable definition; Encoding Standard — TextDecoderOptions / error mode

Context

Receiver: any Request instance — constructed directly, cloned from another request, or handed to a service worker fetch handler.

Exposure: Request is exposed in both Window and Worker contexts ([Exposed=(Window,Worker)] in Chromium's request.idl; the WPT suite runs with global=window,worker). No secure-context requirement and no permission prompt.

Availability: Chrome 151 per the milestone listing and BCD; gated at build time by the TextStreamMethod runtime feature (status stable at trunk).

Source: Chromium request.idl; WPT textstream.any.js; Chromium runtime_enabled_features.json5; chromestatus.com/feature/5146752165478400

Lifecycle

Immediate consumption — non-null bodies only: for a request with a body, calling textStream() starts the read at once — the WPT contract asserts bodyUsed becomes true synchronously.

Reads are destructive to a non-null-body request: once the body is consumed (or even just disturbed by the call), the request is unusable — passing it to fetch() or calling any other body method hits the same TypeError contract. Read a request body only when you own its remaining lifetime; clone it first if it must also be sent. Bodyless requests are exempt: nothing exists to disturb, so repeated calls simply return new closed streams.

Stream lifetime: the returned stream is the body stream piped through the decoder, following ordinary pipe semantics for locking and close.

Source: WPT textstream.any.js — bodyUsed contract; Fetch Standard — textStream() method steps + unusable definition

Examples

// Inspect an upload payload as text before deciding whether to send it.
const request = new Request("/api/upload", {
  method: "POST",
  body: JSON.stringify({ hello: "world" }),
});

// Reading consumes the body — clone if the request must still be fetch()ed.
const copy = request.clone();
const reader = copy.textStream().getReader();
let text = "";
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  text += value; // value is a string, UTF-8 decoded
}
console.log("payload was:", text);
await fetch(request); // original still unused — safe to send
Source: WPT textstream.any.js — request-body read shape; Fetch Standard — textStream() method steps

Compatibility

Request.textStream() — from BCD api/Request.json (checked 2026-07-26)
Engine / runtimeSupportNotes
Chrome151Matches the Chrome 151 milestone listing (Enabled by default); Chrome for Android and WebView (Android + iOS) are BCD mirrors
EdgemirrorBCD mirrors Chrome's data
FirefoxNot supportedBCD version_added: false (Firefox for Android mirrors); vendor signal recorded on the ChromeStatus entry: No signal
SafariNot supportedBCD version_added: false (Safari on iOS mirrors); the ChromeStatus entry records a Positive signal — a recorded signal, not an official WebKit standards position (a feature this small has none)
Node.js26.5.0BCD records version_added: 26.5.0
DenoUnknownNo BCD entry as of 2026-07-26

Support rows are from BCD only. Separately, vendor signals come from the ChromeStatus API feature record (checked 2026-07-26): Firefox “No signal”, Safari “Positive”, web developers “No signals”. No Baseline entry: the webstatus.dev query returns no feature (checked 2026-07-26).

Source: BCD api/Request.json; ChromeStatus API feature record; webstatus.dev query

Security and privacy

No new authority: the method needs no permission or gesture — it only re-reads bytes the page already placed (or received) in the request body, the same trust boundary as request.text().

Deterministic decoding: UTF-8 always, regardless of the request's Content-Type or charset, so decoding cannot be renegotiated per request; malformed bytes become U+FFFD replacement text rather than an exception channel.

Disclosure/storage: reading is local — nothing is transmitted by this method (transmission is fetch()'s job), nothing is persisted, and stream lifetime is bounded by the request's own lifetime.

Source: Fetch Standard — textStream() method steps; Encoding Standard — TextDecoderStream error mode