# WebTransport API cheat sheet

URL: https://webtransport.day/cheatsheet/
Checked: 2026-10-28

## Create a session

### Connect with the WebTransport constructor

The URL must use https and an explicit port; a bad URL or a fragment throws SyntaxError. MDN currently marks this 'Baseline 2026, Newly available' (since March 2026), with a note that parts of the feature have varying support, and it is secure-context only.

```js
const transport = new WebTransport("https://example.com:4433/echo");
await transport.ready; // resolves once the session is established
console.log("connected");
```

Sources: [WebTransport() constructor (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/WebTransport); [WebTransport.ready (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/ready); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Trust a certificate by hash with serverCertificateHashes

Replaces Web PKI validation for this connection. MDN says the certificate must be X.509v3 with under two weeks of validity and an ECDSA P-256 key, and the option cannot be combined with allowPooling: true. Only sha-256 is defined.

```js
const certHash = new Uint8Array(32); // placeholder: SHA-256 of your server certificate
const transport = new WebTransport("https://example.com:4433/echo", {
  serverCertificateHashes: [{ algorithm: "sha-256", value: certHash }],
});
await transport.ready;
```

Sources: [WebTransport() constructor (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/WebTransport); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Hint a congestion control preference

Only a hint; the browser may fall back to "default". MDN currently marks this 'Limited availability' (not Baseline) on the congestionControl property page, and the spec flags "low-latency" as a feature at risk because no browser implements a low-latency algorithm yet.

```js
const transport = new WebTransport("https://example.com:4433/echo", {
  congestionControl: "low-latency", // "default" | "throughput" | "low-latency"
});
await transport.ready;
console.log(transport.congestionControl); // the preference as the browser reports it
```

Sources: [WebTransport() constructor (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/WebTransport); [WebTransport.congestionControl (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/congestionControl); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Refuse a reliable-only fallback with requireUnreliable

Defaults to false. MDN marks the reliability property page 'Limited availability' (not Baseline), while the constructor page shows 'Baseline 2026, Newly available' (since March 2026).

```js
const transport = new WebTransport("https://example.com:4433/echo", {
  requireUnreliable: true, // do not fall back to a reliable-only (HTTP/2) session
});
await transport.ready;
console.log(transport.reliability); // "pending" before ready, then "reliable-only" or "supports-unreliable"
```

Sources: [WebTransport() constructor (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/WebTransport); [WebTransport.reliability (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/reliability); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Negotiate an application protocol with protocols

If the server supports none of the offered protocols, ready rejects. Per the spec, duplicates, empty values and values over 512 characters throw SyntaxError. The option is documented by both MDN and the W3C spec.

```js
const transport = new WebTransport("https://example.com:4433/echo", {
  protocols: ["chat", "file-transfer"], // in order of preference
});
await transport.ready;
console.log(transport.protocol); // the server's choice, or "" if it selected none
```

Sources: [WebTransport() constructor (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/WebTransport); [WebTransport API (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

## Session lifecycle

### Watch ready and closed

Both promises exist from construction. closed resolves with { closeCode, reason } and rejects on unexpected closure such as a network failure. MDN currently marks this 'Baseline 2026, Newly available' (since March 2026).

```js
const transport = new WebTransport("https://example.com:4433/echo");
transport.closed
  .then(({ closeCode, reason }) => console.log("closed", closeCode, reason))
  .catch((err) => console.error("closed abruptly", err));
try {
  await transport.ready;
} catch (err) {
  console.error("session failed", err);
}
```

Sources: [WebTransport.ready (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/ready); [WebTransport.closed (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/closed); [WebTransport API (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API)

### React to draining

Draining is only a request: either side may keep using the session, but the app is expected to wrap up soon. The W3C spec lists draining as a session state. MDN's draining page carries no Baseline banner at the moment.

```js
async function migrateOnDraining(url, oldTransport) {
  await oldTransport.draining; // fulfills when the server signals draining
  const next = new WebTransport(url);
  await next.ready;
  oldTransport.close(); // retire the old session once the new one is up
  return next;
}
```

Sources: [WebTransport.draining (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/draining); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Close with a code and reason

closeCode defaults to 0 and the reason is truncated to 1024 UTF-8 bytes per the spec. MDN names the parameter info; the spec calls it closeInfo. MDN currently marks this 'Baseline 2026, Newly available' (since March 2026).

```js
transport.close({ closeCode: 17, reason: "CloseButtonPressed" });
const info = await transport.closed;
console.log(info.closeCode, info.reason);
```

Sources: [WebTransport.close() (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/close); [WebTransport.closed (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/closed); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Inspect reliability after ready

Tells you whether the session can carry unreliable datagrams. MDN currently marks this 'Limited availability' (not Baseline).

```js
await transport.ready; // reliability is "pending" before this point
if (transport.reliability === "reliable-only") {
  // no datagrams: stick to streams
} else {
  // "supports-unreliable": datagrams are available
}
```

Sources: [WebTransport.reliability (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/reliability); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

## Bidirectional streams

### Open a stream, write, then read

Assumes a server that echoes and closes its side. MDN currently marks this 'Baseline 2026, Newly available' (since March 2026) for createBidirectionalStream(), with partial-support asterisk. It rejects with InvalidStateError if the session is closed or failed.

```js
const stream = await transport.createBidirectionalStream();
const writer = stream.writable.getWriter();
await writer.write(new TextEncoder().encode("hello"));
await writer.close(); // tries to send pending data before closing
const reader = stream.readable.getReader();
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  console.log(new TextDecoder().decode(value)); // value is a Uint8Array
}
```

Sources: [WebTransport.createBidirectionalStream() (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/createBidirectionalStream); [WebTransportBidirectionalStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportBidirectionalStream); [WebTransport API (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API)

### Accept server-opened streams

incomingBidirectionalStreams is a ReadableStream of WebTransportBidirectionalStream objects. MDN currently marks this 'Baseline 2026, Newly available' (since March 2026).

```js
const reader = transport.incomingBidirectionalStreams.getReader();
for (;;) {
  const { value: stream, done } = await reader.read();
  if (done) break;
  // each value is a WebTransportBidirectionalStream opened by the server
  stream.readable.pipeTo(stream.writable).catch(console.error); // echo it back
}
```

Sources: [WebTransport.incomingBidirectionalStreams (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/incomingBidirectionalStreams); [WebTransportBidirectionalStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportBidirectionalStream); [ReadableStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)

### Prioritise streams with sendOrder

MDN's createBidirectionalStream() page documents only sendOrder (an integer; its own examples pass a string). The W3C spec also lists sendGroup and waitUntilAvailable, which MDN does not describe on that page.

```js
const urgent = await transport.createBidirectionalStream({ sendOrder: 10 });
const bulk = await transport.createBidirectionalStream({ sendOrder: 1 });
// queued bytes on the higher sendOrder stream are sent first
```

Sources: [WebTransport.createBidirectionalStream() (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/createBidirectionalStream); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

## Unidirectional streams

### Send on a client-opened stream

The returned WebTransportSendStream is itself a WritableStream. MDN currently marks this 'Limited availability' (not Baseline) on the createUnidirectionalStream() page (it is also marked this way on the WebTransportSendStream page).

```js
const stream = await transport.createUnidirectionalStream(); // a WebTransportSendStream
const writer = stream.getWriter();
writer.write(new Uint8Array([65, 66, 67]));
try {
  await writer.close(); // tries to send all pending data first
  console.log("All data has been sent.");
} catch (error) {
  console.error(`An error occurred: ${error}`);
}
```

Sources: [WebTransport.createUnidirectionalStream() (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/createUnidirectionalStream); [WebTransportSendStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportSendStream)

### Receive server-opened unidirectional streams

The outer loop awaits each stream in turn; handle streams concurrently in real code. MDN currently marks this 'Baseline 2026, Newly available' (since March 2026) for incomingUnidirectionalStreams, whereas the WebTransportReceiveStream page is marked 'Limited availability' (not Baseline).

```js
const reader = transport.incomingUnidirectionalStreams.getReader();
for (;;) {
  const { value: receiveStream, done } = await reader.read();
  if (done) break;
  const chunks = receiveStream.getReader(); // a WebTransportReceiveStream
  for (;;) {
    const r = await chunks.read();
    if (r.done) break;
    console.log(r.value); // Uint8Array
  }
}
```

Sources: [WebTransport.incomingUnidirectionalStreams (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/incomingUnidirectionalStreams); [WebTransportReceiveStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportReceiveStream)

### Abort a send stream

With abort() the browser may discard data that has not yet been sent. Without a WebTransportError reason, the spec sends error code 0. See the Errors section for aborting with a code.

```js
const stream = await transport.createUnidirectionalStream();
const writer = stream.getWriter();
writer.write(new Uint8Array([65, 66, 67]));
await writer.abort(); // pending data may be discarded
```

Sources: [WebTransport.createUnidirectionalStream() (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/createUnidirectionalStream); [WritableStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

## Datagrams

### Send datagrams with createWritable()

MDN calls the datagrams.writable fallback "deprecated and non-standard" and prefers createWritable(); the W3C spec text I read lists only createWritable(). MDN currently marks this 'Baseline 2026, Newly available' (since March 2026) for the datagrams property.

```js
const writable =
  typeof transport.datagrams.createWritable === "function"
    ? transport.datagrams.createWritable()
    : transport.datagrams.writable; // deprecated and non-standard
const writer = writable.getWriter();
await writer.ready;
writer.write(new Uint8Array([65, 66, 67])).catch(() => {});
```

Sources: [WebTransport.datagrams (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/datagrams); [WebTransportDatagramDuplexStream.createWritable() (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportDatagramDuplexStream/createWritable); [WebTransportDatagramDuplexStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportDatagramDuplexStream); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Read incoming datagrams

Datagrams are unreliable: not guaranteed to arrive, and not in order. Only works on sessions that support unreliable transport.

```js
const reader = transport.datagrams.readable.getReader();
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  console.log(value); // Uint8Array
}
```

Sources: [WebTransport.datagrams (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/datagrams); [WebTransportDatagramDuplexStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportDatagramDuplexStream)

### Respect maxDatagramSize

maxDatagramSize is the largest outgoing datagram, in bytes, that may be written. It is read-only.

```js
const writer = transport.datagrams.createWritable().getWriter();
const payload = new Uint8Array(200);
if (payload.byteLength <= transport.datagrams.maxDatagramSize) {
  await writer.ready;
  writer.write(payload).catch(() => {});
}
```

Sources: [WebTransportDatagramDuplexStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportDatagramDuplexStream); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Expire stale datagrams with incomingMaxAge and outgoingMaxAge

Both are read/write and measured in milliseconds. Per the spec, 0 is stored as null, and a negative or NaN value throws RangeError.

```js
transport.datagrams.incomingMaxAge = 2000;  // milliseconds
transport.datagrams.outgoingMaxAge = 2000;
console.log(transport.datagrams.outgoingMaxAge); // null when no maximum age is set
```

Sources: [WebTransportDatagramDuplexStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportDatagramDuplexStream); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Datagram queue-size attributes (names differ) (sources differ)

MDN documents incomingHighWaterMark and outgoingHighWaterMark; the W3C spec text I read documents incomingMaxBufferedDatagrams and outgoingMaxBufferedDatagrams (minimum 1) instead. Probe for the name before using either.

```js
const d = transport.datagrams;
console.log("incomingHighWaterMark" in d);        // named by MDN
console.log("incomingMaxBufferedDatagrams" in d); // named by the W3C spec
```

Sources: [WebTransportDatagramDuplexStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportDatagramDuplexStream); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

## Errors and aborting

### Inspect a WebTransportError

source is "stream" or "session"; streamErrorCode is null if unavailable. MDN says the code is in the range 0-255, but the W3C spec defines an unsigned long, so treat the upper bound as unsettled. MDN currently marks this 'Baseline 2026, Newly available' (since March 2026).

```js
try {
  await transport.ready;
} catch (error) {
  if (error instanceof WebTransportError) {
    console.log(error.source, error.streamErrorCode); // "stream" | "session", number | null
  } else {
    throw error; // e.g. a SyntaxError for a bad URL
  }
}
```

Sources: [WebTransportError (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportError); [WebTransportError() constructor (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportError/WebTransportError); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Abort a send stream or cancel a receive stream with a code (sources differ)

Per the spec, abort and cancel use the reason's streamErrorCode when the reason is a WebTransportError, else 0, and clamp to 0-4294967295. The constructor signature disagrees: the spec shows (message, { source, streamErrorCode }) while MDN shows a single init object with message and streamErrorCode.

```js
const send = await transport.createUnidirectionalStream();
await send.getWriter().abort(new WebTransportError("done", { streamErrorCode: 7 }));

// for a server-opened receive stream:
await receiveStream.getReader().cancel(new WebTransportError("unwanted", { streamErrorCode: 7 }));
```

Sources: [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/); [WebTransportError() constructor (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportError/WebTransportError); [WebTransportError (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportError); [WebTransportSendStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportSendStream); [WebTransportReceiveStream (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransportReceiveStream)

## Feature detection

### Check that WebTransport exists

In a worker, use typeof WebTransport !== "undefined" instead, since window is not defined there. The interfaces are exposed in Window and Worker contexts.

```js
if ("WebTransport" in window) {
  // safe to construct a WebTransport
} else {
  // fall back to WebSocket or another transport
}
```

Sources: [WebTransport.supportsReliableOnly (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/supportsReliableOnly_static); [WebTransport (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Check for a secure context

The WebTransport interfaces are marked SecureContext, so they exist only on https pages (or localhost). In workers, check self.isSecureContext.

```js
if (!window.isSecureContext) {
  console.warn("WebTransport needs a secure context (https or localhost)");
}
```

Sources: [Secure contexts (MDN)](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts); [WebTransport (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)

### Probe members that may be missing

Treat newer members as optional. MDN currently marks congestionControl, reliability, createUnidirectionalStream(), WebTransportSendStream and WebTransportReceiveStream as 'Limited availability' (not Baseline), while the main WebTransport page shows 'Baseline 2026, Newly available' with partial-support asterisk.

```js
const hasCreateWritable = typeof transport.datagrams.createWritable === "function";
const hasCongestionControl = "congestionControl" in WebTransport.prototype;
const hasSendGroups = "createSendGroup" in WebTransport.prototype;
const canFallBackToReliableOnly = WebTransport.supportsReliableOnly; // static boolean
```

Sources: [WebTransport (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport); [WebTransport.supportsReliableOnly (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/supportsReliableOnly_static); [WebTransport.datagrams (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/datagrams); [WebTransport.congestionControl (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/congestionControl); [WebTransport (W3C Working Draft)](https://www.w3.org/TR/webtransport/)
