mercure skill
Pushes real-time resource updates from API Platform to clients using the Mercure protocol and Server-Sent Events.
- GitHub stars
- 29
- Files
- 1
- Last commit
- Oct 22, 2026
- License
- —
- Allowed tools
- —
What does the mercure skill do?
mercure pushes real-time resource updates from API Platform to clients using the Mercure protocol and Server-Sent Events.
Summary generated from the skill's SKILL.md.
About the mercure skill
Pushing real-time resource updates to clients from an API Platform backend is what this skill enables for Symfony developers. It uses the Mercure protocol and Server-Sent Events to broadcast changes whenever a resource is created, updated, or deleted. When an agent loads this skill, it can help configure Doctrine lifecycle events and the symfony/mercure-bundle to ensure that serialized resource data is automatically sent to a Mercure hub.
This tool is specifically designed for Symfony environments and relies on a separate Mercure hub process, such as the mercure.rocks binary or Caddy module. It differs from bidirectional messaging systems because it is a one-directional server-to-client push mechanism. Developers can use it to manage public broadcasts or secure, private updates by leveraging JWT claims for authorized subscribers. The skill supports custom topics, specific normalization contexts for leaner payloads, and dynamic options via ExpressionLanguage to scope visibility to specific resource owners.
What an agent can do with it
- 1Implement real-time dashboardsAn agent can help you set up automatic UI updates whenever data changes in your backend.
- 2Secure per-user notificationsYou can configure private updates so that sensitive data only reaches authorized subscribers via JWT claims.
- 3Optimize push payloadsA developer can instruct the agent to use different serialization groups for pushes to keep the data lightweight.
Written from the skill's SKILL.md. Every use case is checked against a quote from it.
How to install the mercure skill
Each command copies the skill folder into the client's skills directory. Restart the client or start a new session to load it.Install the mercure skill in Plugin
/plugin marketplace add api-platform/skillset /plugin install api-platform@api-platform-skillset
Install the mercure skill in Claude Code
~/.claude/skills/mercure/SKILL.md
$ npx degit api-platform/skillset/skills/mercure ~/.claude/skills/mercureInstall the mercure skill in Claude Code (project)
.claude/skills/mercure/SKILL.md
$ npx degit api-platform/skillset/skills/mercure .claude/skills/mercureInstall the mercure skill in Codex
~/.agents/skills/mercure/SKILL.md
$ npx degit api-platform/skillset/skills/mercure ~/.agents/skills/mercureInstall the mercure skill in Gemini CLI
~/.gemini/skills/mercure/SKILL.md
$ npx degit api-platform/skillset/skills/mercure ~/.gemini/skills/mercureInstall the mercure skill in Claude.ai
npx degit api-platform/skillset/skills/mercure mercure && zip -r mercure.zip mercure # claude.ai → Customize → Skills → + → Create skill → Upload a skill → mercure.zip
SKILL.md
View on GitHubMercure: Real-Time Updates
Symfony-only. The Mercure integration is built on Doctrine's lifecycle events and
symfony/mercure-bundle; the API Platform Laravel package ships no Mercure support. On Laravel use Laravel's own broadcasting (Reverb/Echo/Pusher).
Mercure pushes the new version of a resource to all subscribed clients whenever it
is created, updated or deleted. API Platform hooks Doctrine's lifecycle: after a
write it serializes the resource and publishes it to the Mercure hub, which fans it
out to connected clients over Server-Sent Events. Clients subscribe with the native
EventSource API — no extra client library required.
Use this for dashboards, collaborative editing, notifications. It is push-only and one-directional (server → client); for bidirectional messaging you still need something else.
Enabling on a resource
use ApiPlatform\Metadata\ApiResource;
#[ApiResource(mercure: true)]
class Book {}
Now every create/update/delete on a Book publishes to the hub. On delete, only
the (now-gone) IRI is sent so clients can drop it. API Platform also adds a Link
header advertising the hub URL on every response for this resource, so smart
clients auto-discover where to subscribe.
mercureis intentionally untyped — it acceptstrue, an options array, or an ExpressionLanguage string. There is noMercureOptionsattribute class in core; pass a plain array. (Verified againstApiResource::$mercure, declaredmixed.)
Symfony hub configuration
Mercure is preconfigured in the API Platform distribution. With a manual install, add the MercureBundle and point it at a hub:
# config/packages/mercure.yaml
mercure:
hubs:
default:
url: '%env(MERCURE_URL)%' # internal URL API Platform publishes to
public_url: '%env(MERCURE_PUBLIC_URL)%' # URL the browser subscribes to
jwt:
secret: '%env(MERCURE_JWT_SECRET)%'
publish: ['*'] # API Platform must be allowed to publish
The hub itself runs as a separate process/container (the mercure.rocks binary or
the Caddy Mercure module).
Subscribing from a browser
Read the hub URL from the Link header, then open an EventSource on the topic
(the resource IRI):
const url = new URL('https://example.com/.well-known/mercure');
url.searchParams.append('topic', 'https://example.com/books/1');
const es = new EventSource(url);
es.onmessage = ({ data }) => {
const book = JSON.parse(data); // the serialized resource, or {"@id": "..."} on delete
};
Use a topic template like https://example.com/books/{id} to receive updates
for every book in one subscription.
Available options
Pass an array instead of true:
#[ApiResource(mercure: [
'private' => true, // authorized subscribers only (see below)
'topics' => ['https://example.com/books'], // override the default (resource IRI)
'normalization_context' => ['groups' => ['book:push']], // serialize differently for the push
'type' => 'book-updated', // SSE event type
'retry' => 3000, // SSE retry hint (ms)
])]
class Book {}
data (override the published payload) and id (SSE event id) are also accepted.
A common reason to set normalization_context is to push a leaner shape than the
HTTP response, or to avoid pushing fields a subscriber shouldn't compute on.
Private (authorized) updates
A public update reaches anyone subscribed to the topic. For per-user data, mark the
update private so only clients whose JWT carries a matching subscribe target
selector receive it:
#[ApiResource(mercure: ['private' => true])]
class Notification {}
The subscriber's Mercure JWT must include a mercure.subscribe claim matching the
topic. This is the mechanism that prevents one user's real-time updates leaking to
another — pair it with HTTP-level security (see operations) so the resource is
both fetch-protected and push-protected.
Dynamic options via expression
When the options depend on the resource instance (e.g. the topic must include the
owner), use an ExpressionLanguage string resolved per object. iri() builds an IRI,
escape() is rawurlencode:
use ApiPlatform\Metadata\ApiResource;
#[ApiResource(mercure: 'object.getMercureOptions()')]
class Book
{
public function getMercureOptions(): array
{
return [
'private' => true,
// '@=' marks each topic value as an expression
'topics' => [
'@=iri(object)',
'@=iri(object.getOwner()) ~ "/?topic=" ~ escape(iri(object))',
],
];
}
}
The alternate owner/?topic=... form is the "restrictive update" pattern: a
subscriber authorized only for https://example.com/users/foo/{?topic} receives the
update via the alternate topic without being able to subscribe to all books. This is
how you scope a shared resource type to per-owner visibility.
GraphQL subscriptions
When GraphQL is enabled (see graphql), mercure: true also powers GraphQL
subscription operations — clients subscribe to a query and receive pushes through
the same hub via a generated subscription IRI.
Checklist
- Hub configured with separate internal
urland publicpublic_url - API Platform's publisher JWT allows
publish - Per-user resources use
'private' => true, not public topics - Private pushes paired with HTTP
securityon the same resource -
normalization_contextset when the push shape differs from the HTTP response - Clients discover the hub from the
Linkheader, not a hardcoded URL
Files in this skill
1mercure skill questions
How do I install the mercure skill in Claude Code?
Run npx degit api-platform/skillset/skills/mercure ~/.claude/skills/mercure. Claude Code loads skills from ~/.claude/skills (all projects) or .claude/skills (one project) and uses a skill when its description matches the task.
Which clients can use the mercure skill?
Any client that reads the Agent Skills format: Claude Code, Codex, Gemini CLI and claude.ai (upload as a ZIP). Scripts in the skill run only where the client allows code execution.
Is Mercure supported on Laravel?
No, the Mercure integration is Symfony-only. For Laravel, you should use Laravel's own broadcasting features like Reverb, Echo, or Pusher.
How can I ensure real-time updates are private to specific users?
You can mark updates as private by setting 'private' to true in the Mercure options. This ensures only clients with a JWT containing a matching subscribe claim can receive the update.
Can I change the data format sent during a push?
Yes, you can use the 'normalization_context' option to serialize the resource differently for the push, such as sending a leaner data shape.