qoliber / trident-cache-shopware
Shopware 6 plugin for the Trident HTTP cache: a reverse-proxy gateway (cache tags, durable purges to every Trident instance), ESI header/footer through Trident, and admin screens over Trident's admin API.
Package info
github.com/qoliber/trident-cache-shopware
Type:shopware-platform-plugin
pkg:composer/qoliber/trident-cache-shopware
Requires
- php: ~8.2.0||~8.3.0||~8.4.0
- guzzlehttp/guzzle: ^7.5
- guzzlehttp/psr7: ^2.4
- qoliber/trident-php: ^1.7
- shopware/administration: ~6.7.0
- shopware/core: ~6.7.0
- shopware/storefront: ~6.7.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-28 07:43:04 UTC
README
Full-page caching for Shopware 6.7 behind the Trident
HTTP cache: Shopware's cache tags on every page, purges delivered durably to
every Trident instance, the header and footer as ESI fragments, and
administration screens over Trident's admin API. Built on
qoliber/trident-php.
Tested: Shopware 6.7.14.2, PHP 8.3, Trident 1.8.0 (tests/shopware6-e2e).
How it plugs in
Shopware 6.7 has a reverse-proxy mode for Varnish and Fastly: Shopware stops caching pages itself and hands tags and invalidations to a reverse-proxy gateway. The plugin switches that mode on and registers a Trident gateway:
| Shopware 6.7 extension point | What the plugin does |
|---|---|
shopware.http_cache.reverse_proxy.enabled |
turned on by the plugin's Resources/config/packages/trident_cache.yaml (a project's own config/packages still wins) |
AbstractReverseProxyGateway::tag() |
writes the page's tags to X-Cache-Tags: bounded to Trident's 200, with an overflow tag, prefixed if you share a Trident between shops, plus the tags of blocks rendered inline |
::invalidate() / ::banAll() |
records the purge in the trident_purge_outbox table, one row per instance |
::flush() (end of request, after the delayed-invalidation task) |
delivers the rows; each is removed only when that instance acknowledged it. It then delivers up to 50 due rows of earlier requests, and schedules each delivered purge once more 10 s later (a page rendered from data the purge's transaction had not yet made visible) |
::ban() (media URLs) |
URL purge on every instance |
scheduled task trident.purge_outbox_drain (60 s) |
retries unacknowledged rows with backoff |
kernel.request |
announces ESI (Surrogate-Capability) for requests from Trident without a sw-cache-hash |
kernel.response (after Shopware's cache subscriber) |
a render for a logged-in customer → private, no-store; drops the session cookie from shared responses |
Why an outbox: Shopware's own delayed invalidation deletes the tags from
invalidation_tags before it purges, and its task handler swallows errors.
A refused or unreachable Trident would silently keep stale pages. With the
outbox, trident:purge:status shows every purge still owed.
Install
composer require qoliber/trident-cache-shopware bin/console plugin:refresh bin/console plugin:install --activate TridentCache bin/console cache:clear
Trident: start from ../trident.toml. It has the
Shopware-specific keys: vary_cookies = ["sw-cache-hash"], the Vary header
allowlist, ESI propagate_headers, and the rule that passes logged-in visitors.
Configure
The connection lives in the deployment config (environment, .env.local),
which takes precedence over Settings → Extensions → Trident Cache:
# One or more Trident instances. JSON: single-quote it in .env files. TRIDENT_INSTANCES='{"edge-1":{"api_url":"http://10.0.0.11:9301"},"edge-2":{"api_url":"http://10.0.0.12:9301","api_token":"other"}}' TRIDENT_API_TOKEN=the-admin-auth_token # default for instances that name none TRIDENT_PEERS=127.0.0.1, 10.0.0.11, 10.0.0.12 # where Trident connects FROM (ESI); IPs, CIDRs, host names # Optional: TRIDENT_API_URL (single instance), TRIDENT_PURGE_MODE=soft|hard, # TRIDENT_TAG_PREFIX=shop1_ (several shops on one Trident), TRIDENT_ESI=0|1
- Instance names: 1–64 characters of
A-Z a-z 0-9 . _ -. Invalid entries are skipped and reported bytrident:checkand the administration. - Which token goes where:
TRIDENT_API_TOKEN(or a per-instanceapi_token) is used only with instances from the environment (TRIDENT_INSTANCES,TRIDENT_API_URL), never with the URL entered in the administration. Someone who can change the plugin settings can never make the deployment's token travel to another host.- The token entered in the administration is stored encrypted and bound
to the API URL saved with it: XChaCha20-Poly1305 with the URL as
associated data, the key derived from
APP_SECRET(orTRIDENT_TOKEN_KEY).
- What that means in practice:
- The settings form shows a placeholder, and the system-config search API only ever sees ciphertext.
- Changing the URL makes the stored token unusable until you re-enter
it. So does rotating
APP_SECRET/TRIDENT_TOKEN_KEY.trident:checkthen says "re-enter the token", and no token is sent.
TRIDENT_ALLOWED_API_HOSTS=trident, 10.0.0.12:9301(optional, and recommended in production): every instance URL whose host (orhost:port) is not listed is refused. See Security.
Security
The API URL is a setting an administrator can change, and the shop's server
then makes requests to it (a server-side request): without limits, anyone with
system_config write access could point it at an internal service and read
the answer on the Trident screens. The plugin limits this in four ways:
- Set
TRIDENT_ALLOWED_API_HOSTSin production. It is the only setting that confines the API URL to the hosts you run Trident on; everything else below only narrows what a wrong URL can do. Or configure the instances in the environment (TRIDENT_INSTANCES), which the administration cannot change. - The URL's shape:
http(s)://host[:port][/base-path]only — no query, fragment or credentials. - Never link-local or cloud metadata (
169.254.0.0/16,fe80::/10,fd00:ec2::254,100.100.100.200,metadata.google.internal), even when allowlisted. It is checked on the resolved address at request time, and the connection is pinned to the address that was checked, so DNS rebinding cannot swap it. Private ranges stay allowed: Trident normally is internal. - Nothing unverified is displayed. A screen shows data from an instance only after it answered as a Trident admin API (a version and a known cache mode); an error body from anything else is clipped to 300 characters, without control characters. No redirect is followed, and the token is sent only to the URL it was sealed with.
Cache context
| Visitor | Page |
|---|---|
| anonymous | the shared page (HIT), header/footer as ESI fragments |
| cart, other currency, rule-dependent context | Shopware requires its sw-cache-hash: a shared variant per context, cached (every shopper with the same context shares it) |
logged-in (sw-states=logged-in) |
passed to Shopware; the render is private, no-store (it names the customer) |
The cart and account widgets load over AJAX (/widgets/...), so shared pages
never carry anyone's cart.
Commands
| Command | |
|---|---|
trident:check |
reach every instance, show version and license |
trident:purge:status |
outbox per instance; exit 1 on a skipped instance, rows owed to a removed one, or a purge pending > 15 min |
trident:purge:drain [--force] [--now] |
deliver due rows (--force ignores the backoff; --now delivers every row at once, second deliveries and backstop rows included) |
trident:purge <tags…> / --all |
purge Shopware tags / the whole shop through the outbox |
trident:purge:forget <instance> |
drop the rows of an instance that is no longer configured |
trident:warm [--limit] |
queue the home page and canonical SEO URLs on every instance's warmer |
Administration
Settings → Extensions → Trident Cache has these screens:
- Dashboard
- Purge (URL, product, category, tag, pattern with preview, host, whole shop)
- Cached pages with entry detail, Tags, Coverage, Warmer
- Launch mode, Reflect mode, Denoisers (with this shop's WAF export), Bans, Backends, DNS discovery, Live events
Product and category pages get a Purge from Trident button.
- Per-instance: every screen shows each instance separately; an unreachable one is reported, never fatal.
- Privileges (roles: Trident Cache):
trident_cache:read(viewer) for the screens;trident_cache:update(editor) for every action.
- Confirmation: actions that change what every visitor is served require it, and the server enforces it.
- Validation: URLs must be on this shop's domains. Denoiser pins need one of this shop's hosts: the engine keys pins by the request host.
Known limits (Trident 1.8)
-
Live events poller: the bounded reader of the event stream (
Admin/EventPoller.php) is plugin code today, as in the WooCommerce and Magento integrations. It moves intoqoliber/trident-phpin library 1.7. -
Warmer sitemap source: Shopware writes its sitemap only as
.xml.gz, which the 1.8 warmer does not decompress. Usetrident:warm. -
ESI in assemble mode: Trident's
assemblemode does not carry fragment tags into the assembled page. Usehole_punch, or switch the plugin's ESI off withassemble. -
Duplicate purges: every stored invalidation also gets a backstop outbox row, due after Shopware's own task. It covers the moment when Shopware has deleted its tags but not yet purged. So each change is purged twice: once by Shopware's task, then about 6 minutes later by the backstop. A duplicate purge is harmless.
Versioning
Versions follow Trident: this plugin 1.8.x works with Trident 1.8. MAJOR.MINOR moves with the engine (every Trident X.Y.0 release is also a release of this package, changed or not); the PATCH number is this package's own. The admin screens warn when a connected Trident runs another release line.
This repository is a mirror
qoliber/trident-cache-shopware is developed in the Trident repository together with the
shared library qoliber/trident-php
and the live end-to-end test stacks, and published to
github.com/qoliber/trident-cache-shopware automatically:
every commit there is a "Sync from trident-cache@…" snapshot. Please open
issues there; pull requests against the mirror cannot be merged, because the
next sync would overwrite them. Releases are the tags of that repository.