Workers closes the gap on WinterCG's baseline API list
Cloudflare's participation in the W3C WinterCG community group has moved from announcement to implementation. The group's first concrete output, the Minimum Common Web Platform API, is a short catalog of standard interfaces that any runtime claiming web interoperability should support. Workers now has compliant or near-compliant coverage of every item on that list.
The WinterCG compiled this baseline by checking which standard APIs already existed in at least two of the major non-browser runtimes: Node.js, Deno, Bun, and Workers. Beyond the core list, the group also expects runtimes to expose atob(), btoa(), queueMicrotask(), structuredClone(), timer functions such as setTimeout() and setInterval(), console, and crypto.subtle on the global scope.
Where Workers does not exactly match the specifications, the divergence is intentional: backward compatibility constraints, Worker-specific features, or optimization trade-offs. Still in progress are the areas where the runtime has not yet caught up to the current spec text.
This is not only a compatibility exercise. Several of the upgrades needed to meet the WinterCG baseline touch core runtime behavior: event dispatch, cancellation semantics, character encoding, URL handling, and stream processing. The sections below highlight some of the standards work that has happened in the runtime in the past few months. The common thread is that the platform is moving ever closer to the Web Platform APIs, while remaining faithful to the constraints of the Workers execution model.
Standards compliance push in the Workers runtime
The Workers runtime has long aimed to keep its developer experience aligned with JavaScript and Web Platform standards. Over the last year, that effort has focused on both bringing existing APIs like Event, EventTarget, URL, and streams closer to spec, and adding new standard APIs such as URLPattern, encoding streams, and compression streams.
Event and EventTarget
Workers has included Event and EventTarget since the beginning, but those were minimal implementations covering only what the runtime itself needed. The WHATWG DOM spec defines a much richer interface, with properties such as type, target, currentTarget, bubbles, cancelable, defaultPrevented, composed, isTrusted, and timeStamp, plus methods like composedPath(), stopPropagation(), and stopImmediatePropagation(). Most of these were originally absent because the runtime didn't need them internally.
const event = new Event('foo', {
bubbles: false,
cancelable: true,
composed: true,
});
console.log(event.bubbles);
console.log(event.cancelable);
console.log(event.composed);
addEventListener('foo', (event) => {
console.log(event.eventPhase); // 2 AT_TARGET
console.log(event.currentTarget);
console.log(event.composedPath());
});
dispatchEvent(event);
That changed with a more complete implementation. All standard, non-legacy members are now exposed, and a long-standing bug preventing user code from subclassing Event for custom event types was fixed. That fix is gated behind a compatibility flag now enabled by default for Workers with a compatibility date on or after 2022-01-31.
EventTarget was also brought up to standard, adding support for once handlers, cancelable handlers via AbortSignal, and event listener objects. Once handlers automatically unregister after the first invocation, which helps prevent memory leaks when an event will only fire once. Cancelable handlers let you remove a listener on demand, and listener objects are an alternative to functions — the standard allows any object with a handleEvent() method to be passed to addEventListener().
const listener = {
handleEvent(event) {
console.log(event.type);
}
};
addEventListener('foo', listener);
addEventListener('bar', listener);
dispatchEvent(new Event('foo'));
dispatchEvent(new Event('bar'));
AbortController and AbortSignal
Support for AbortController and AbortSignal is new. The pattern is straightforward: an AbortSignal is an EventTarget that emits a single "abort" event when triggered, and an AbortController performs the triggering. A reason argument — typically an Error, but any JavaScript value — can be passed along with the abort event.
Signals fire only once. They can also be created with a timeout via AbortSignal.timeout(10), or pre-triggered on creation with AbortSignal.abort('for reasons') (these never actually emit the event). Within Workers, the APIs are integrated with EventTarget, fetch(), and streams.
const ac = new AbortController();
const res = fetch('https://example.org', {
signal: ac.signal
});
ac.abort(new Error('canceled'))
try {
await res;
} catch (err) {
console.log(err);
}
Encoding and compression streams
The existing TextEncoder and TextDecoder implementations previously only handled UTF-8. The standard TextDecoder covers a broader range of encodings, and that full set is now supported. TextEncoder remains UTF-8-only per the spec.
const { writable, readable } = new TextDecoderStream("windows-1251");
const writer = writable.getWriter();
writer.write(new Uint8Array([
207, 240, 232, 226, 229, 242, 44, 32, 236, 232, 240, 33,
]));
const reader = readable.getReader();
const res = await reader.read();
console.log(res.value); // Привет, мир!
The new TextEncoderStream and TextDecoderStream are TransformStream implementations for streaming encoding and decoding. They require the transformstream_enable_standard_constructor compatibility flag.
Streaming compression and decompression are also available through the standard CompressionStream and DecompressionStream APIs, both fully conformant TransformStream implementations. These require no compatibility flag.
const ds = new DecompressionStream('gzip');
const decompressedStream = blob.stream().pipeThrough(ds);
const cs = new CompressionStream('gzip');
const compressedStream = blob.stream().pipeThrough(cs);
URL parsing corrected and URLPattern added
The original URL implementation in Workers had subtle spec deviations. For instance, https://a//b//c// was incorrectly normalized to https://a/b/c (dropping empty path segments), while the standard algorithm yields https://a//b//c/. Such differences caused interoperability problems across JavaScript runtimes.
A new, spec-compliant URL parser is now enabled by default for Workers deployed on or after October 31, 2022. Older Workers can opt in by updating their compatibility date or enabling the url_standard compatibility flag.
Alongside this, the standard URLPattern API is now available. It provides regex-like pattern matching for URLs, as shown in MDN's example:
// Matching a pathname
let pattern1 = new URLPattern('https://example.com/books/:id')
// same as
let pattern2 = new URLPattern(
'/books/:id',
'https://example.com',
);
// or
let pattern3 = new URLPattern({
protocol: 'https',
hostname: 'example.com',
pathname: '/books/:id',
});
// or
let pattern4 = new URLPattern({
pathname: '/books/:id',
baseURL: 'https://example.com',
});
Standard constructors for streams
The biggest change is the rewrite of ReadableStream, WritableStream, and TransformStream. Previously, user code couldn't construct custom readable or writable streams, and transform streams were limited to byte pass-throughs. The new implementations are near-complete per the stream standard, with a few edge cases still in progress.
Custom stream constructors support regular and byte ReadableStream types, BYOB readers, and optimized tee() and pipeThrough() paths. The new code becomes the default on November 30, 2022, or can be enabled earlier via the streams_enable_constructors and transformstream_enable_standard_constructor flags.
async function handleRequest(request) {
const enc = new TextEncoder();
const rs = new ReadableStream({
pull(controller) {
controller.enqueue(enc.encode('hello world'));
controller.close();
}
});
return new Response(rs);
}
Writable streams accept any JavaScript value, and custom transform streams can now be built with full constructor arguments instead of just the old no-arg pass-through form. The original no-arg pass-through behavior is preserved in a new IdentityTransformStream class, but note one difference: the old non-standard TransformStream supported BYOB reads on the readable side, while the standard implementation does not.
Looking ahead
Work continues on fetch() and WebSockets, and the team is working with other runtime implementers through the Web-interoperable Runtimes Community Group to drive broader alignment. The Cloudflare Workers Runtime team is also hiring for these efforts.



