JavaScript WebTransport API Table

PieceWhat it doesField note
new WebTransport(url)The handshakeHTTPS/3 mandatory; await wt.ready; self-signed certs only via serverCertificateHashes (SHA-256 pinning, 14-day cap)
wt.datagramsThe lossy laneroughly 1200-byte cap, unordered, unacknowledged, never retransmitted - a stale cursor is worse than a lost packet
createBidirectionalStream()The reliable lanesindependent ordered byte channels; server-initiated ones arrive on incomingBidirectional/UnidirectionalStreams
the reliability splitThe architecturestate ticks ride datagrams, commands and payloads ride streams - write the split into the protocol from day one
no head-of-line blockingThe QUIC giftstreams multiplex independently: a stalled stream 3 does not delay stream 4 - the disease a TCP-backed WebSocket cannot cure
congestionControl optionThe dialdefault vs low-latency at construction: quicker sends vs throughput; interactive picks low-latency, bulk transfer does not
wt.close() / wt.closedThe exitclose({closeCode, reason}); closed resolves with close info; nothing auto-reconnects - wrap your own backoff and re-sync
the support truthThe matrixChromium-only today (Chrome/Edge 97+); no polyfill can fake unordered unreliable delivery; UDP-blocked networks fail silently, keep the WebSocket ladder
Reference: the MDN WebTransport API. Both halves of the network in one connection: reliable ordered streams AND unreliable datagrams over HTTP/3 - client-server, low latency, honest about which messages can afford to be lost.
Bottom line: choose lanes per message type - datagrams for state that expires in milliseconds, streams for anything that must arrive. Assume the session dies: no auto-reconnect means your connection manager owns backoff, re-sync and protocol versioning. And keep the fallback ladder real - WebSockets everywhere, WebTransport where loss-tolerant latency wins and you control the server.
Related tools: the websocket table (the everywhere-reliable ladder), the WebRTC table (the peer-to-peer sibling), the streams table (the Readable/Writable surface), the network information table (the signal that picks the lane), and the WebCodecs table (the realtime media that rides it).

WebTransport is a client-server transport over HTTP/3 (QUIC) that finally gives the browser both halves of the network: reliable ordered streams AND unreliable unordered datagrams, multiplexed over one connection without head-of-line blocking. Where WebSockets give you a single reliable byte pipe (a dropped packet stalls everything behind it) and WebRTC data channels give you unreliable but with a peer-to-peer complication, WebTransport is client-server, low-latency, and honest about which messages can afford to be lost.

Bottom line: the architecture decision is the reliability split. Game state updates, cursor positions, live sensor ticks, telemetry - datagrams (transport.datagrams.writable / readable), where a stale packet is worse than a lost one and no retransmit clogs the line. Chat, file chunks, control commands - streams (transport.createBidirectionalStream() or the server-initiated variants), each independent, so one slow stream does not stall its siblings the way a TCP-backed WebSocket would. The connection handshake is HTTPS to a server that must present a certificate (self-signed allowed, pinned via serverCertificateHashes) - deployment is a server decision, not a CDN toggle.

The honest part: Chromium-only today (Chrome/Edge 97+, behind the maturity of the HTTP/3 ecosystem), no Safari or Firefox shipping it, and no polyfill can deliver what makes it valuable - unordered, unreliable, multiplexed delivery is a network-layer property. The fallback ladder is real, though: WebSockets cover the reliable half everywhere, and a feature-detect plus transport-choice layer means the same app protocol runs over both.

How to use

  1. Connect and wait: const wt = new WebTransport('https://server.example.com:4433/wt'); await wt.ready; - HTTPS mandatory, the server answers the HTTP/3 extended CONNECT; self-signed certs work only when you pass serverCertificateHashes (SHA-256 pinning).
  2. Send lossy and fast: const writer = wt.datagrams.writable.getWriter(); await writer.write(encoded); - datagrams are capped (~1200 bytes safe) unordered and unacknowledged; read them from wt.datagrams.readable with a reader loop.
  3. Open reliable lanes: const stream = await wt.createBidirectionalStream(); await stream.writable.getWriter().write(chunk); - each stream is independent; read the server's own streams from wt.incomingBidirectionalStreams / incomingUnidirectionalStreams.
  4. Choose per message type: telemetry and state ticks ride datagrams (drop stale, never queue), commands and payloads ride streams - write this split into the protocol from day one; retrofitting it is a rewrite.
  5. Close with intent: await wt.closed (a WebTransportCloseInfo) or call wt.close({closeCode, reason}) - for long-lived servers, watch session draining and re-connect logic (exponential backoff) because the API auto-reconnects nothing.

Frequently asked questions

When do I pick WebTransport over WebSockets - or over WebRTC?

Split by who owns the other end and what each message can afford. WebSockets: one reliable ordered pipe - perfect for chat, JSON APIs, anything where ordering matters and the volume is modest; the cost is head-of-line blocking, because one lost TCP packet stalls the whole pipe. WebRTC data channels: unreliable/unordered with configurable retransmission, but peer-to-peer machinery (signaling, STUN/TURN) you only want when peers talk to each other. WebTransport: client-to-server, low-latency, and both reliability modes at once - the fit is server-relayed realtime where state updates can drop (datagrams) while control traffic stays reliable (streams), all without head-of-line blocking between them. The pragmatic ladder: start with WebSockets if reliability alone suffices; reach for WebTransport when latency spikes under packet loss are measurably hurting you and you control the server; keep WebRTC for actual peer connections.

How do datagrams and streams actually differ in behavior?

Datagrams are fire-and-forget messages: capped at roughly one QUIC packet (~1200 bytes to be safe), not retransmitted, not ordered, not flow-controlled beyond a small send queue - write a stale cursor update and the network just skips it, which is the point. Streams are independent reliable byte channels: ordered within themselves, retransmitted on loss, flow-controlled, and multiplexed - a stalled upload on stream 3 does not delay stream 4 (this is the head-of-line-blocking fix vs a single TCP connection). The design consequence is protocol-level: tag each message with what it is (state vs command vs payload) and route it to the right lane; mixing everything into streams recreates a TCP-shaped app, while putting payloads in datagrams loses files. Backpressure exists on both: datagram writers reject when the queue is full (drop and move on - your loss budget), stream writers honor await write() as the pacing signal.

What is the certificate story for servers in development and production?

WebTransport requires HTTPS/3, but it deliberately supports non-public CAs: a development server with a self-signed certificate works if the client pins it - generate the cert, hash it (SHA-256), and pass serverCertificateHashes: [{algorithm: 'SHA-256', value: hash}] in the WebTransport constructor; the browser then accepts exactly that cert for exactly that session. In production, a normal publicly-trusted certificate makes the hashes unnecessary (and hashes are capped at 14 days validity by spec - they are a dev bootstrap, not a PKI replacement). The deployment prerequisites that actually bite: the server must speak HTTP/3 (QUIC) and answer the extended CONNECT upgrade - frameworks like quic-go, aioquic and echo servers for Rust/Go provide it; UDP must not be firewalled on the path (corporate networks block it, which is why your fallback transport matters); and CDNs are only beginning to offer WebTransport pass-through, so self-hosting is currently the norm.

How should reconnection and backpressure be designed?

Assume the session dies and design the join-back as a first-class flow. The API gives you wt.closed (a promise resolving with close info or rejecting on failure) and nothing automatic: no reconnect, no resumption token exposed to apps - so wrap the transport in a connection manager with exponential backoff and jitter, a fresh WebTransport per attempt, and a re-sync step after ready (a 'since' cursor or snapshot request) because datagrams you missed are gone forever. Backpressure discipline: for datagrams, treat writer.write() rejection or a full queue as your signal to drop stale messages (keep only the newest state per key); for streams, await each write and let the natural pacing push back on producers; and watch transport.congestionControl ('default' vs 'low-latency' at construction) - the low-latency option trades throughput for quicker sends, right for interactive apps, wrong for bulk transfer. Above all, version the handshake: the first reliable message on connect should declare protocol version and capability flags, so old clients and new servers negotiate instead of corrupting each other.

Related tools