Version 1.4.3
July 21, 2026
Added
edge/cache/refresh-expiredpurges pages whose content changed status on its own: a scheduled post going live, an entry expiring. Craft fires no event at those moments, so run this on a schedule (a per-minute cron is fine — it is two indexed lookups).
Fixed
- Project config changes (fields, entry types, sections, site settings) now invalidate.
Changed
edge_cachesgains anexpiryDatecolumn; the schema version is now 1.1.0.
Version 1.4.2
July 21, 2026
Fixed
- Content inside a
{% cache %}block is now tracked, so pages using the tag invalidate. - Invalidation triggered inside a queue job is no longer discarded.
- Pricing change invalidation for Commerce catalog pricing rules.
- End-of-request flush handlers no longer accumulate in long-lived queue workers.
Changed
- Pages whose
{% cache %}blocks run unscoped element queries now invalidate more widely, because those dependencies are recorded for the first time. Scope the queries to narrow it. - A change resolving to at least half the cached tier is flushed wholesale rather than as thousands of individual purge and warm jobs. Same purge scope, one job.
configWarnings()documented its return type as the verify result; corrected tostring[].
Version 1.4.1
July 20, 2026
Added
docs/reverse-proxy.mddocuments the Apache h2c trap. Withmod_http2enabled (the Debian/Ubuntu default shipsProtocols h2 h2c http/1.1), Apache answers plain HTTP withUpgrade: h2,h2c+Connection: Upgrade, which breaks nginx keepalive reuse: reused connections return200with an empty body. Small JSON responses fail most of the time while full pages mostly succeed, soedge/hydrate,edge/csrfand Craft's ownsession-infosilently return nothing and every form on a cached page stops working. Pin the backend vhost toProtocols http/1.1.
Changed
- The bundled example app now reflects current behaviour: templates use
{{ edgeCsrfInput() }},config/edge.phpsetscacheLoggedInRenders,config/general.phpcarries the correcttrustedHosts/ipHeaderspairing, and the nginx config gained the cache-key-aware bypass rule, a/actions/location and browser revalidation on cache hits.
Version 1.2.0
July 20, 2026
Added
{{ edgeCsrfInput() }}Twig function. Emits a token-free placeholder on a page that is being cached (the hydration runtime fills it per visitor) and the real token inline on a page that isn't — an excluded URI, a bypass, a signed-in render, an island fragment. A shared form partial no longer needs a flag threaded through it to say which kind of page it landed in, and the uncached case costs no round trip and needs no JavaScript.- The Edge Cache utility and
edge/nginx/verifynow warn whenqueryStringCachingisrespecton thenginx-staticdriver. That combination silently writes one file per query string that the tier can never look up, because it serves<host>/<uri>/index.htmlwith no place for a query segment.
Fixed
- The shipped nginx configs now bypass the cache for any request carrying a query param
that isn't in
excludedQueryStringParams. Without it,/shop?brand=xwas answered from the unfiltered/shopentry, sinceignoremode leaves the query string out of the key.tests/guards.phpasserts the map and the origin guard agree case by case. location @edgenow sendsCache-Control: public, max-age=0, must-revalidateon a hit. nginx serves the stored file verbatim, so the origin's headers never reached the visitor and browsers fell back to heuristic freshness — holding pages for an unpredictable window that no purge could reach.
Changed
Plugin::proxyWarnings()is nowPlugin::configWarnings()and covers driver/query-mode mismatches as well as trusted-proxy problems.
Version 1.1.0
July 20, 2026
Fixed
- A response rendered for a
Hostother than the site's configured base-URL host is no longer stored. The cache file is keyed by the site's own host while Craft renders absolute URLs from the requestHost, so a request arriving on another host (a bare IP, a forgedHost) could write its URLs into the canonical entry and have them served to every visitor. Such requests still render normally. - Cacheable responses now send
public, s-maxage=<cacheControlTtl>, max-age=0, must-revalidateinstead ofpublic, max-age=<cacheControlTtl>.max-ageis a browser directive, and a purge can never reach a browser: the previous header let a visitor hold stale HTML for the full TTL (a year, by default) with no way to recall it. - In
queryStringCaching: 'ignore'mode, a request carrying a query param that isn't inexcludedQueryStringParamsis no longer stored. The query string is dropped from the cache key in that mode, so/shop?brand=xwas being written over the entry for plain/shop. Marketing params are excluded by design and still cache. - CSRF hydration no longer races island hydration. The token is fetched once and applied
again to each island fragment after it swaps in, before
edge:islandfires, so forms inside islands are filled. - CSRF hydration now fills Craft's async
<craft-csrf-input>placeholder, which Formie also emits and whichasyncCsrfInputsproduces. Previously only plain inputs were filled.
Added
cacheLoggedInRenderssetting (defaultfalse). The edge tier already serves the shared copy to signed-in visitors, but their renders were never stored, so a page browsed only by signed-in staff never warmed. Enable it when the shell is identity-independent.- A response containing a rendered CSRF token (an input named after
csrfTokenNamewith a non-empty value) is refused and logged instead of being stored with a token baked in. - A response containing the signed-in user's email, username or full name is refused and
logged when
cacheLoggedInRendersis on. The matched field name is logged, never its value. This catches the obvious leak only; it cannot see group-scoped pricing or permission-scoped elements. X-Edge-Skip-Reasonheader on non-cacheable responses indevMode, naming the rule that matched.- The Edge Cache utility and
edge/nginx/verifywarn whentrustedHostscontainsanywhileipHeadersis set, which lets any client spoof its IP throughX-Forwarded-For. window.EdgeCsrf.ensure(root)/.apply(root)for filling CSRF fields in markup a site injects itself.docs/reverse-proxy.md, coveringtrustedHosts/ipHeadersfor both proxy topologies.- The shipped nginx configs gained a
location ^~ /actions/block, adefault_servercatch-all recipe, and a reverse-proxy variant for origins that aren't FastCGI. Without a.phphandler,try_files $uriresolves/index.phpto the file on disk and nginx serves the PHP source. tests/guards.php, a framework-free self-check for the pure guards (php tests/guards.php).
Changed
CacheDriverInterface::prepareResponse()takes an optional fourth argument,?string $skipReason. Drivers extendingBaseDriverneed no change; a driver implementing the interface directly must update its signature.
Version 1.0.0
July 17, 2026
- Initial release.