CDN Cache-Tag Headers

Emit a Cache-Tag response header on every frontend HTML request, giving your CDN per-resource tags to purge against - “flush everything tagged lang:fr_FR” or “flush post:42” without nuking the whole edge cache.

Disabled by default. Enable via PerfLocale → Settings → Advanced → CDN Cache-Tag Headers, or programmatically with add_filter( 'perflocale/cache_tags/enabled', '__return_true' ). When off, no send_headers callback is registered and no class is loaded.

What gets emitted

One Cache-Tag header per response, with a comma-separated list of tags for that request’s context:

Cache-Tag: perflocale,lang:fr_FR,lang-slug:fr,post:42,post-type:post
AlwaysPer-context
perflocale
lang:<locale>
lang-slug:<slug>
Singular: post:<id>, post-type:<type>
Term archive: tax:<taxonomy>, term:<term_id>
Post-type archive: archive:<type>
Author archive: author:<id>
Date archive: date:YYYY-MM
Search: search
404: 404
Home/front page: home

CDN support matrix

CDNHeader nameOut of the box?
Cloudflare (Enterprise Cache Tags)Cache-TagYes
Bunny.netCache-TagYes
FastlySurrogate-KeyFilter perflocale/cache_tags/header_name
KeyCDNCache-TagYes
AkamaiEdge-Cache-TagFilter perflocale/cache_tags/header_name

For Fastly:

add_filter( 'perflocale/cache_tags/header_name', fn() => 'Surrogate-Key' );

Sanitisation + limits

  • Tags are filtered to [A-Za-z0-9\-_:.] only - no whitespace, quotes, commas, or control characters
  • Per-tag length capped at 128 characters
  • Maximum 32 tags per response
  • Header value is hard-capped at 8000 bytes (filterable via perflocale/cache_tags/max_header_length); tags overflowing the budget are dropped silently
  • Header is never emitted on admin, AJAX, cron, REST, XMLRPC, or feed requests; also skipped for paths in the routing-exclusion list

Customising the tag list

add_filter( 'perflocale/cache_tags/tags', function ( array $tags ): array {
	$tags[] = 'theme:' . get_template();

	if ( is_singular( 'product' ) ) {
		$product = wc_get_product( get_queried_object_id() );
		foreach ( $product->get_category_ids() as $cat_id ) {
			$tags[] = 'wc-cat:' . (int) $cat_id;
		}
	}

	return $tags;
} );

The filter runs after PerfLocale’s sanitisation pass, so your custom tags go through the same [A-Za-z0-9\-_:.] guard before emission.

Purging from your CDN

PerfLocale does not issue remote purges - that’s host-specific and there are plenty of existing plugins for each CDN. Instead, PerfLocale fires an action with the tags that need flushing:

add_action( 'perflocale/cache_tags/purge', function ( array $tags ): void {
	// Cloudflare example via an existing integration plugin.
	do_action( 'cloudflare_purge_tags', $tags );
} );

This fires on relevant content-change hooks (post save, term edit, translation linked) so sibling-language variants get purged alongside the source content.

Disabling per path

If you need to suppress the header for specific routes without turning the whole feature off, add the path to Settings → URL & Routing → Excluded Paths. PerfLocale uses the same exclusion list for routing and for Cache-Tag emission.

  • Hooks reference - all four filters + the purge action
  • Edge integration - pair Cache-Tag headers with edge-based language routing for near-100% edge cache hit rates