Mercure Setup
Direct Edit reports back to the browser through a Mercure hub: editing started, the upload finished, the session was cancelled. Without a hub, a session starts but the browser never hears the result.
There is one hub and three setup steps. Steps 1 and 2 apply to every installation. Step 3 is a single extra setting
that only frontends built on the deprecated FileEditControllerTrait need, and it disappears with them in 2027.1.
1 Run a Mercure Hub
Run an up-to-date Mercure instance, for example the official Docker container. See the Mercure docs for the installation details.
Pimcore Studio uses the same hub, and the Studio Backend bundle documents it as well, see Studio Backend Mercure Setup. Run one hub for both. The worked example below is kept here because Direct Edit adds requirements the Studio setup does not have: it only works over HTTPS, and a legacy frontend subscribes anonymously (see step 3).
Example Configuration with docker compose
The snippets below set up Mercure with docker compose. Mercure is exposed only through an nginx reverse proxy running over HTTPS; internal communication runs over HTTP, which helps when working with self-signed certificates in development.
docker-compose.yaml
Add mercure to your docker compose file and make sure to add the configuration for JWT keys.
# add here all the other necessary containers...
# configure nginx to run under https
nginx:
image: nginx:stable-alpine
ports:
- 443:443
depends_on:
- php-fpm
volumes:
- ./demo-px-enterprise:/var/www/html:ro
# mount nginx configuration to be adapted (see below)
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
# certificates for running nginx (for direct edit to work, system has to run with https)
- ~/.certs:/etc/nginx/certs
mercure:
image: dunglas/mercure
restart: unless-stopped
environment:
# Disable HTTPS
SERVER_NAME: ':80'
# Add JWT keys configured
MERCURE_PUBLISHER_JWT_KEY: '<YOUR_JWT_KEY>'
MERCURE_SUBSCRIBER_JWT_KEY: '<YOUR_JWT_KEY>'
nginx.conf
Configure reverse proxy in nginx to route mercure traffic accordingly.
server {
# (...)
location /hub {
proxy_pass http://mercure/.well-known/mercure;
}
# Thumbnails
# (...)
}
With this snippet the hub is reachable at https://your-app-domain.com/hub, because nginx replaces the /hub prefix
with the hub's own path. Remember which of the two shapes your hub answers on: it is the URL you configure in step 2,
and a legacy frontend defaults to the other one, see step 3.
Check if the Hub Is Running
Call the hub URL in the browser. The response has to be:
Missing "topic" parameter.
Run the same request from the server's command line, to confirm the hub is reachable by Pimcore itself:
curl https://your-app-domain.com/hub
A 404 means the URL does not match the proxy layout: with the location /hub snippet above, /hub is the hub and
/hub/.well-known/mercure is not.
Running Mercure with an External URL
When running Mercure as an external service with an external URL, make sure to configure CSP properly.
Pimcore Configuration:
pimcore_studio_ui:
csp_header:
additional_urls:
connect-src:
- 'https://<MERCURE_URL>/.well-known/mercure'
Mercure Configuration, sample based on docker compose, important part is the cors_origins directive:
mercure:
image: dunglas/mercure
restart: unless-stopped
environment:
# All other necessary configs ...
# ...
# Add cors configuration
MERCURE_EXTRA_DIRECTIVES: |-
cors_origins "https://<YOUR_PIMCORE_URL>"
Webserver Reverse Proxy
With the Mercure default URLs, a reverse proxy routes the Mercure requests to the Mercure instance. Communication has to run over HTTPS, so place the Mercure server behind the reverse proxy of the web server that handles the certificates.
Apache, with http_proxy enabled:
ProxyPass /hub/ http://mercure/.well-known/mercure
ProxyPassReverse /hub/ http://mercure/.well-known/mercure
Nginx:
location /hub {
proxy_pass http://mercure/.well-known/mercure;
}
Shipped Mercure Binary
The bundle ships the Mercure binary for backwards compatibility only. It is untested, may no longer work, and may contain security vulnerabilities. Do not use it in production.
The bundle ships a Mercure executable as a fallback. Run it with:
./vendor/pimcore/direct-edit/bin/mercure --jwt-key=your-256-bit-secret-min-32-chars --addr=':3000' --debug --allow-anonymous --cors-allowed-origins='*' -f --debug
The hub has to stay up permanently. This crontab entry restarts it if it died:
*/5 * * * * /usr/bin/flock -n /tmp/mercure.lockfile /<system-path>/mercure --jwt-key=your-256-bit-secret --addr=':3000' --debug --allow-anonymous --cors-allowed-origins='*' -f
2 Connect Pimcore to the Hub
Pimcore's connection to the hub belongs to the Studio Backend bundle and is configured once, under
pimcore_studio_backend.mercure_settings:
pimcore_studio_backend:
mercure_settings:
jwt_key: '<YOUR_JWT_KEY>'
hub_url_client: 'https://your-app-domain.com/hub' # or <PIMCORE_SCHEMA_HOST>/hub
hub_url_server: 'http://mercure/.well-known/mercure'
Use <PIMCORE_SCHEMA_HOST> for hub_url_client when one installation serves several hosts, for example a Pimcore
Studio backend and portals on their own domains. See
Studio Backend Mercure Setup
for the full reference.
Direct Edit adds no configuration of its own here. It publishes each session's events to the Studio user who started the session, so Pimcore Studio and a Portal Engine portal on the Studio frontend receive them with nothing further to set up. A session started through your own permission service carries your prefix instead of Studio's and the bundle does not publish for it; forwarding those events to your users is part of the integration, see Integrate into Custom Application.
Direct Edit only works when Pimcore is served over HTTPS, development systems included. The desktop client refuses anything else. See Compatibility.
3 One Extra Setting for Legacy Frontends (deprecated)
The bundle's own hub client (PublishService, MercureUrlService, the topic
http://www.pimcore.com/direct-edit/client-upload/user/<userId>), the pimcore_direct_edit.mercure_settings
configuration and the parameters.mercure.hub.* keys are deprecated since 2026.3 and will be removed in 2027.1. They
serve only frontends built on FileEditControllerTrait. Setting pimcore_direct_edit.mercure_settings logs a
deprecation. See the upgrade notes.
A legacy frontend does not use the connection from step 2. It subscribes to the bundle's own topic, on a URL the bundle
resolves separately, and that URL does not fall back to hub_url_client. Its default is
https://<host>/hub/.well-known/mercure, which is the wrong shape for the proxy layout in step 1. So unless your hub
answers on exactly that path, a legacy frontend needs client_side_url:
pimcore_direct_edit:
mercure_settings:
# The hub URL the legacy frontend subscribes to. Deprecated; removed in 2027.1.
client_side_url: '<PIMCORE_SCHEMA_HOST>/hub'
Its subscription is anonymous, because the legacy events are published as public updates and no subscriber cookie is issued for them any more. The hub therefore has to accept anonymous subscribers:
mercure:
environment:
MERCURE_EXTRA_DIRECTIVES: |-
anonymous
The directive applies to the whole hub, which Studio shares. Studio's own updates are published privately and stay protected, but every public update on that hub becomes readable without a token, the legacy topic's asset id, file name and modification date included. One more reason to skip this step unless a legacy frontend still needs it.
Do I Need This Step?
You need it if any frontend in the installation is built on FileEditControllerTrait. Two are:
- Portal Engine portals on the Classic frontend. Classic is the default, and the frontend is chosen per portal under General Settings → Frontend Variant (empty means Classic), but a query parameter, a cookie and a per-user setting can each override that per request. Auditing every portal is therefore not conclusive. Until you have moved every portal to the Studio frontend, do this step: it is one setting, it changes nothing for portals already on the Studio frontend, and it goes away with the legacy frontend.
- Your own frontend, if you built it on the trait. See Integrate into Custom Application for the migration.
If neither applies, skip this step entirely. The bundle still publishes the legacy topic until 2027.1, but nothing subscribes to it and nothing here has to be configured.
Overrides and Settings Without Effect
Everything else under pimcore_direct_edit.mercure_settings and parameters.mercure.hub.* only overrides step 2, or
does nothing at all:
| Setting | Effect | Status |
|---|---|---|
mercure_settings.client_side_url | The hub URL the legacy frontend subscribes to. Not derived from hub_url_client. | needed as described above, deprecated |
mercure_settings.server_side_url | Publishes the legacy topic to a different hub than hub_url_server. | optional override, deprecated |
parameters.mercure.hub.jwt_key, or publisher_jwt_key and subscriber_jwt_key | Signs the legacy publisher's tokens with a different key than jwt_key. | optional override, deprecated |
mercure_settings.jwt_cookie_host, jwt_cookie_strictness | None. The endpoint that issued the legacy cookie was removed with the classic admin UI in 2026.1. Studio's cookie is configured under pimcore_studio_backend.mercure_settings. | no effect, deprecated |