Skip to content

$ cat mercure-10-alpha-is-here.md

Mercure 1.0 alpha is here

Mercure 1.0 alpha, now in public preview, replaces 0.x URI Templates with the WHATWG URL Pattern standard and two matcher types, exact and urlpattern. It moves authorization to OAuth 2.0 access tokens, adds a split debugger and playground, and tightens security defaults. Existing 0.x clients can connect through a compatibility mode.

dunglas.dev11 Aug 2026 · 8 min read · summary
Read on dunglas.dev ↗
Mercure 1.0 is here in its first public preview, and it is the biggest release in the project’s history! Mercure powers mission-critical real-time communication across hundreds of production deployments. It delivers Server-Sent Events over plain HTTP without requiring client SDKs. Version 1.0 maintains this architecture while modernizing core capabilities using newly established web standards: Standardized authorization: Fully aligned with OAuth 2.0. Modern topic matching: Driven by the WHATWG URL Pattern standard. IETF standardization: Re-engineered as an Internet-Draft submitted for official RFC consideration. Built-in debugging: Includes a completely redesigned UI debugger for monitoring message flows. This release also introduces updated security hardening and a rewritten documentation suite. The demo hub at demo.mercure.rocks already runs 1.0 alpha. Try it while you read! Upgrading your infrastructure requires no immediate changes to existing applications. Enabling compatibility mode allows your current 0.x clients and authentication tokens to connect to a 1.0 hub without modifications, letting your team migrate on your own schedule. A real matcher system, built on a web standard Mercure 0.x was using URI Templates to match several topics at once using patterns. While convenient, it was definitely a hack. The URI Templates RFC even explicitly states that regular expressions languages are better suited for matching: Some URI Templates can be used in reverse for the purpose of variable matching: comparing the template to a fully formed URI in order to extract the variable parts from that URI and assign them to the named variables. Variable matching only works well if the template expressions are delimited by the beginning or end of the URI or by characters that cannot be part of the expansion, such as reserved characters surrounding a simple string expression. In general, regular expression languages are better suited for variable matching. We were aware of this limitation from the very beginning, but we lacked a proper alternative. The web platform fixed it by introducing the WHATWG URL Pattern standard in October 2023, and it reached the web platform baseline in September 2025. Mercure 1.0 is now built entirely on this new standard, providing full compatibility with URL Pattern supported by web browsers and a wide range of other platforms. Also, topic matching used to be a grab bag of selectors: URI Templates and raw, exact matching strings were conflated in the same “topic” query parameter. 1.0 replaces all of it with exactly two matcher types, and every hub supports both: exact: case-sensitive, byte-for-byte topic comparison. urlpattern: the WHATWG URL Pattern standard, the same syntax browsers ship natively. So a subscriber writes the pattern the platform already understands: // Before (0.x) url.searchParams.append("topic", "https://example.com/books/{id}"); // After (1.0) url.searchParams.append("match_urlpattern", "https://example.com/books/:id"); Under the hood, the Mercure hub uses my free and open source go-urlpattern library, which implements the full URL Pattern test suite and powers URL matching in the Caddy web server. This is a real standard your tools already understand, not a Mercure-only hack. Query parameter names are case-sensitive, and an unknown name under the match prefix returns 400, so a typo fails loudly instead of silently matching nothing. Pattern matching runs under a linear-time guard, so a crafted pattern can’t hang the hub. OAuth 2.0 authorization: simpler, safer, standard The bespoke mercure JWT claim is gone. The hub is now an OAuth 2.0 protected resource, and tokens are standard RFC 9068 access tokens (typ: at+jwt) carrying iss, aud, and an RFC 9396 authorization_details claim: { "type": "https://mercure.rocks/authorization-detail", "actions": ["publish", "subscribe"], "topics": [ {"match": "https://example.com/books/1"}, {"match": "https://example.com/books/:id", "match_type": "urlpattern"} ] } Two things matter here. First, self-issued tokens stay a first-class citizen. Sign tokens in your app with a key, exactly as before, now in a standard shape. A new CLI mints them for you: caddy mercure-token \ --iss https://example.com \ --aud https://hub.example.com/.well-known/mercure \ --key @publisher.pem --alg ES256 \ --publish https://example.com/books/1 \ --subscribe-urlpattern 'https://example.com/books/:id' Second, you can now bring your own OAuth 2.0 authorization server. Point the hub at a jwks_uri, advertise the authorization server in the hub’s RFC 9728 metadata, and let it issue tokens for Mercure. The Caddyfile makes both models explicit: mercure { issuer {$MERCURE_TRUSTED_ISSUERS:https://localhost} { publisher { jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} } subscriber { jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG} } } } Each issuer binds the iss value a token carries to the keys used to verify it, so a token is only ever checked against the key that belongs to its issuer. There is nothing more to configure than in 0.x: the hub derives its public URL and its token audience from each request, so a hub reachable on several domains works with no extra setup. Auth failures follow RFC 6750 (401 invalid_token, 403 insufficient_scope, 400 invalid_request), and the token no longer travels in the URL. In a browser it rides a __Secure-mercure_access_token cookie. A brand new debugger The built-in UI has been rebuilt and split in two: The debugger at /.well-known/mercure/debug/ is safe to run in production. Open live streams and publish updates with a token you provide. It never mints one for you. The playground is a development-only mode. It mints an all-access token, prefills it in the UI, and opens the hub up so you can experiment in seconds. Turn it on with MERCURE_EXTRA_DIRECTIVES=playground, and keep it off on any hub that serves real users. Open the debugger on the demo hub right now: demo.mercure.rocks. Hardened by default Security got a hard look across the board: Signing algorithms come from an explicit allowlist. The hub never reads the algorithm from the token header. A PEM key can no longer be paired with HS*, which closes the classic RS-to-HS algorithm-confusion attack. Access tokens no longer travel in the URL (RFC 9700). In the browser they use a __Secure--prefixed cookie (opt-out possible). An unknown directive inside the mercure block is now a hard error, so a typo can’t silently disable a security setting. The subscription API carries a reconciliation cursor on the rel="mercure" Link header, and the hub exposes Link cross-origin, so a browser client can hand back last_event_id and avoid missed messages. A new spec, and an updated hub The protocol has been rewritten IETF-style and published as an Internet-Draft on track for RFC publication, with IANA registries so anyone can add new matcher types and actions. The hub itself is still a custom Caddy build, so automatic TLS, HTTP/3, advanced compression (including using Zstandard), as well as generic and Mercure-specific Prometheus metrics and OpenTelemetry spans come with it. Two protocol features are worth (re)discovering while you’re here: Active subscriptions: the hub can publish a private update whenever a subscription starts or ends, so you get a live “who is connected” view without a separate service. The hub assigns the subscriber identifier, and clients cannot forge it. Canonical and alternate topics: a single publish can list a topic more than once. The first is canonical, the rest are alternates, and the hub delivers to subscribers matching any of them. Docs rebuilt around modern use cases The documentation has been fully revamped and reorganized around what people build today. There are now dedicated guides for streaming LLM tokens to the browser and for reporting AI agent progress in real time, both over plain Server-Sent Events with native EventSource and no client SDK. Built-in reconnection and replay mean a dropped connection resumes without losing a token. Still 100% compatible with 0.x Migrating a large codebase takes time, so 1.0 keeps a foot in both worlds. Turn on compatibility mode and every 0.x client and token keeps working against a 1.0 hub: protocol_version_compatibility 8 It is off by default because it relaxes token validation (it drops the required exp and the aud / iss / at+jwt checks, and re-accepts the token in the query string), so treat it as a bridge while you migrate, not a destination. The upgrade guide has the full 0.x to 1.0 mapping. Almost five years in the making The idea behind the new matcher system is not new. The request to move Mercure off URI Templates and onto URL Patterns was filed as issue #588 in November 2021, almost five years ago. It waited for the web platform to catch up, as URL Pattern gained maturity, and for a full rethink of authorization to make the rest fall into place. The specification grew up in the open. It went through nine revisions as an IETF Internet-Draft, from draft-00 to the current draft-dunglas-mercure-08, each one tightening conformance and closing security gaps. The implementation followed the same loop: land a change, review it against the draft, harden it, test it, repeat. OAuth conformance fixes came straight out of the draft-08 review, per-issuer key binding and the request-derived hub URL landed as separate passes, and compatibility mode was reworked until it could carry 0.x traffic without weakening the 1.0 path. The last stretch was intense. Since the final 0.x release in June, 1.0 took 94 commits across 25 pull requests, and three alphas shipped in under a week to shake out the new debugger, the token CLI, and the playground. This alpha is where all of it comes together in public for the first time. Improved PHP ecosystem integration Mercure was created to address a fundamental limitation of traditiona

source

This is a summary of an article first published by dunglas.dev. The full piece is on their site.

Open original