Skip to content

Kévin Dunglas

Founder of Les-Tilleuls.coop (worker-owned cooperative). Creator of API Platform, FrankenPHP, Mercure.rocks, Vulcain.rocks and of some Symfony components.

Menu
  • Talks
  • Resume
  • Sponsor me
  • Contact
Menu

Mercure 1.0 alpha is here

Posted on August 11, 2026August 11, 2026 by Kévin Dunglas

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 traditional PHP: the inability to maintain persistent client-server connections. Since then, it has proven valuable across a wide range of tech stacks—either because they face similar constraints (such as serverless platforms) or simply because managing secure, scalable real-time connections is inherently difficult, and Mercure makes it easy.

While Mercure is now widely adopted across Go, JavaScript, Python, and Ruby communities, it remains especially popular within the PHP ecosystem. Notably, FrankenPHP, the modern PHP application server supported by the PHP Foundation, embeds a Mercure hub by default and uses it under the hood to power its Hot Reload feature.

Mercure is natively supported by Symfony and API Platform, and we have worked hard to ensure both frameworks fully support the new 1.0 protocol. The Symfony Mercure component, the Symfony Mercure Bundle have been updated for full compatibility with the 1.0 alpha release of the hub and protocol. Support in API Platform is being worked on!

See it live at API Platform Con

The opening keynote at API Platform Con is all about Mercure 1.0. Join us in Lille, France, or online on September 17 and 18, 2026 for the deep dive, the demos, and the roadmap, and meet the team in person.

Try it now

Managed, on Mercure Cloud, where 1.0 alpha is an opt-in per project: mercure.rocks.

Self-hosted, in one command:

docker run -p 80:80 -p 443:443 -p 443:443/udp \
  -e MERCURE_EXTRA_DIRECTIVES=playground \
  dunglas/mercure

This is an alpha, so expect changes before the final 1.0.0. The playground, its minted token, and compatibility mode sit outside the backward-compatibility promise. Bug reports and feedback are welcome on GitHub.

Related posts:

  1. Mercure 0.14: Major Performance Improvement and New Features
  2. Symfony and API Platform get “push” and real-time capabilities (Mercure protocol)
  3. Say Hello to Mercure 0.10!
  4. The Mercure.rocks Hub is now based on Caddy Web Server

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Social

  • Bluesky
  • GitHub
  • LinkedIn
  • Mastodon
  • X
  • YouTube

Links

  • API Platform
  • FrankenPHP
  • Les-Tilleuls.coop
  • Mercure.rocks
  • Vulcain.rocks

Tags

Apache API API Platform Buzz Caddy Docker Doctrine FrankenPHP Go Google GraphQL HTTP/2 Hydra hypermedia Javascript JSON-LD Kubernetes La Coopérative des Tilleuls Les-Tilleuls.coop Lille Linux Mac Mercure Mercure.rocks MySQL performance PHP Punk Rock Python React REST Rock'n'Roll RSS Schema.org Security SEO SEO Symfony Symfony Live Sécurité Ubuntu webperf Wordpress XHTML XML

Archives

Categories

  • DevOps (87)
    • Ubuntu (68)
  • Go (21)
  • JavaScript (46)
  • Mercure (8)
  • Opinions (91)
  • PHP (179)
    • API Platform (81)
    • FrankenPHP (16)
    • Laravel (1)
    • Symfony (98)
    • Wordpress (6)
  • Python (14)
  • Security (15)
  • SEO (25)
  • Talks (47)
© 2026 Kévin Dunglas | Powered by Minimalist Blog WordPress Theme