Skip to main content
Version: Next

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

warning

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.

info

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)

warning

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:

SettingEffectStatus
mercure_settings.client_side_urlThe hub URL the legacy frontend subscribes to. Not derived from hub_url_client.needed as described above, deprecated
mercure_settings.server_side_urlPublishes 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_keySigns the legacy publisher's tokens with a different key than jwt_key.optional override, deprecated
mercure_settings.jwt_cookie_host, jwt_cookie_strictnessNone. 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