Exclude Pages, Cookies and Parameters
Everything that keeps a page out of the cache, and how to add your own. Configured under Exceptions > Cache exceptions, one entry per line (commas also work).
The five lists
| Label | Setting | Matching | Use for |
|---|---|---|---|
| Paths that must stay dynamic | cache.bypass_paths | Segment boundaries | Whole sections: /account/, /members/ |
| Cookie prefixes that bypass cache | cache.bypass_cookies | Prefix of the cookie name | Any visitor with state |
| Never cache query parameters | cache.bypass_query_params | Exact parameter name | Actions: add-to-cart, preview |
| Cache each value separately | cache.vary_query_params | Exact parameter name | Parameters whose every value is its own page for everyone: orderby, a product filter, lang |
| Parameters that do not change content | cache.ignored_query_params | Exact parameter name | Tracking noise you want stripped while the page stays cached |
The defaults are listed on Page Cache. Core WordPress paths, the login cookie and the preview parameters are already in them.
Path matching
Matching is on segment boundaries, so /checkout/ protects /checkout, /checkout/, and everything below it, but not /checkout-summary. That is the reason a naive prefix match is wrong: /cart should not accidentally bypass /cartography. Paths are compared case-sensitively and without the query string.
A bypass path of / protects the site root only, not the whole site.
The unknown_query rule
Any query parameter that is not on the ignored list or the cache-separately list causes a bypass. This is the behavior that surprises people most, and it is deliberate.
- Nobody can fill your disk by requesting
?a=1,?a=2,?a=3. - A parameter your site genuinely uses can never be silently dropped into a wrong cache hit.
The cost is that a legitimate parameter you forgot to declare bypasses instead of caching. That is the right direction to fail in.
To cache across a parameter, add it to Parameters that do not change content: the page is cached once and the parameter is stripped from the key. Only list parameters that never change the page. Ignoring one that changes price, language or content serves the wrong HTML.
When each value really is a different page, and the same for every visitor who sends it, add the parameter to Cache each value separately instead: every value gets its own stored copy. Values over 100 characters are not cached, and a page keeps at most 100 variants; beyond that, new values are served uncached. Purging the page removes all of its variants. See Page Cache.
To name a parameter that must always bypass, add it to Never cache query parameters. Parameter names match case-insensitively.
A parameter sent as an array (?name[]=value) always bypasses the cache (query_array:<name>) and is sent no-store, whichever list it is on.
Cookies
Prefix match on the cookie name, so wordpress_logged_in_ covers every hashed variant. Enter names or stable prefixes, never values. This is why a signed-in administrator never sees a cache hit, and why testing must be done logged out.
The managed Cloudflare rule excludes a request when the cookie text appears anywhere in its Cookie header, which is at least as broad as the origin’s prefix match. See Cloudflare.
What is excluded automatically
- Everything the active commerce adapters declare. See Commerce Safety.
- Jetpack’s visitor-state cookies, while Jetpack is active and Jetpack compatibility is on. See Integrations.
- Requests with an
Authorizationheader, requests that are notGETorHEAD, and requests for a host this site does not answer to. - Admin, AJAX and cron requests, feeds and
robots.txt. - Any response that sets a cookie, is not HTTP 200, is not
text/html, or carriesno-storeorprivateinCache-Control. - Any response rendered while
DONOTCACHEPAGEis true. - Any post with Don’t cache this page ticked in the editor’s GT Performance box (reason
page-option). See Page Cache.
The lists also reach beyond the page cache. Bypass paths are removed from WordPress speculative loading (WordPress 6.8+), so the browser never prefetches a cart or account page. A front-end request that matches a bypass path, cookie or query rule from these lists or a commerce adapter, or that sends a parameter as an array, is sent with no-store headers and X-GT-Commerce-Cache: BYPASS, even while the origin page cache is off or in optimize-only mode.
Per-response exclusion from code
// Anywhere before the response is captured
if ( my_page_is_personalised() ) {
define( 'DONOTCACHEPAGE', true );
}
The page renders normally and is not stored. Setting a cookie in the response has the same effect.
Adding rules from code
Two filters carry the cache policy, and a rule added in code needs both:
gt_performance_cache_policyis read while WordPress runs: the capture decision,cache explain, warming, speculative-loading exclusions, and the Cloudflare rule when it is synced.gt_performance_compiled_configshapes the encrypted config the drop-in reads before WordPress loads. Itscachekey is the same policy.
The commerce adapters hook both. Do the same:
function my_site_cache_rules( array $policy ): array {
$policy['bypass_paths'][] = '/members/';
$policy['bypass_cookies'][] = 'my_session_';
return $policy;
}
add_filter( 'gt_performance_cache_policy', 'my_site_cache_rules' );
add_filter( 'gt_performance_compiled_config', function ( array $config ): array {
$config['cache'] = my_site_cache_rules( (array) $config['cache'] );
return $config;
} );
The compiled config is rewritten on every settings save, on plugin activation and deactivation, and when the active commerce policy changes. After adding the code, save settings once so the runtime file is rewritten, then purge the page cache (Purge GT cache or wp gt-performance cache purge) so no old copy of /members/ survives. A save that changes no setting does not purge by itself. Then run Connect/sync Cloudflare or wp gt-performance cloudflare sync so the edge rule includes the new paths.
With only the first filter, WordPress never stores the page, but a copy stored before you added the rule is still served by the drop-in until it expires or is purged. Adding a path only in the Cloudflare dashboard leaves your origin caching it.
Checking your work
wp gt-performance cache explain --page-url=https://example.com/members/
prints the decision as JSON: cacheable, the deciding reason (for example path:/members/), the cache key, the state of the origin entry, and whether the managed Cloudflare rule agrees with the origin for that URL. The URL must be on this site’s home host.
Tools > Explain this page shows the same decision in wp-admin.
To see the decision on a live response, turn on Diagnostic logging (Optimization tab, Diagnostics) and look for X-GT-Cache: BYPASS and X-GT-Cache-Reason, which carries the reason as written, for example cookie:my_session_. A bypass cookie is easiest to test with curl:
curl -sI -H 'Cookie: my_session_abc=1' https://example.com/ | grep -i '^x-gt'
Turn Diagnostic logging off again when you are done.