WP Cache Autopilot – Documentation
Developer Reference
Updated:
Architecture Overview
WP Cache Autopilot follows a pipeline architecture:
Cache Invalidator is organized around:
- Listeners
Hook into WordPress lifecycle events (post, taxonomy, comment, meta, presentation). - GroupResolver
Maps post types to configured groups/scopes and resolves target pages. - InvalidationEngine
Core decision logic for post-based invalidation. - RelationshipResolver
Strategy-based host-post discovery. - ArchiveResolver
Post type and taxonomy archive URL resolution. - PurgeCoordinator
Adapter-aware purge execution. - EmissionHelper
Centralized URL emission. - AsyncInvalidationQueue/Processor
Background deep invalidation planning and relationship processing. - TimedInvalidationScheduler
Cron-based scheduled invalidation. - Lifecycle
Plugin activation, deactivation, and version upgrade reconciliation.
Cache Warmup is organized around:
- Triggers
Event intake from Cache Invalidator,save_post, and adapter purge hooks, with frontend eligibility filtering to prevent editor infrastructure from entering the warmup queue. - Queue
Persistent option-based state management for run intent, preparation state, buffered targeted URLs, and execution queues. - Runner
Run preparation, lifecycle transitions, and batch execution via WP-Cron with lease locking and eager state persistence. - TuningStats
Rolling telemetry window for adaptive auto-pacing. - Sitemap
URL resolution and membership filtering. - PriorityFilter
Snapshot-driven URL ordering by priority pages, post type, and language. - Logger
Custom DB table for run history and per-URL event details. - Lifecycle
Plugin activation, deactivation, DB migration, and version reconciliation.
Terminology
| Term | Definition |
|---|---|
| Invalidation | Deterministic decision of which URLs must be refreshed based on a detected change |
| Purge | Removal of cache entries executed by the active cache plugin |
| Warmup | Rebuilding cache by issuing HTTP requests to previously purged URLs |
| Resolution | Process of determining affected URLs from a trigger (includes relationships, archives, and structural usage) |
| Trigger | System event indicating a potential change (e.g., post update, option change, structural modification) |
| Targets / Target URLs | Final set of URLs produced by resolution and passed to execution |
| Target pages (Group context) | User-defined pages selected within a Group configuration as invalidation targets |
| Relationship propagation | Expansion of targets through content relationships (host ↔ related content model) |
| Cache adapter | Integration layer translating purge instructions into cache-plugin-specific operations |
| Targeted warmup | Execution mode where only resolved target URLs are warmed after invalidation |
| Full warmup | Execution mode where all known URLs (e.g., sitemap set) are warmed independent of a specific change |
| Async deep invalidation | Deferred background resolution of complex target sets (e.g., relationship graphs) via WP-Cron to avoid blocking request lifecycle |
| Global invalidation | Strategy where all cache is purged due to uncertainty or system-wide change |
| Global fallback | Safety mechanism ensuring non-content or uncertain triggers never resolve to zero targets |
| Execution (Warmup execution) | Processing of resolved URLs into actual HTTP requests via the warmup queue |
| Queue (Warmup queue) | Deterministic batching system that schedules and processes warmup URLs over time |
| Host post type | Content type whose pages are being invalidated |
| Related post type | Content type that influences host pages through relationships |
Actions
Action: ekesto_ci_invalidation_urls
Primary integration point.
Fires after Cache Invalidator resolves an invalidation event and emits the final result. Purge may or may not have happened (e.g. external cache events).
Cache Warmup uses this as its targeted warmup (cache preload) intake.
Signature
add_action(
'ekesto_ci_invalidation_urls',
function (array $urls, array $context): void,
10,
2
);Parameters
| Parameter | Type | Description |
|---|---|---|
$urls | array | Flat list of absolute URLs to warm. Empty ( []) means full/global invalidation. |
$context | array | Invalidation event metadata (see table below) |
context.urls is always present.
If no URLs exist for a targeted event, nothing is emitted.
$context keys
| Key | Description |
|---|---|
source | Cache adapter key. |
source_label | Human-readable cache adapter name. |
reason | Invalidation reason key from emission context (built-in runtime keys + custom extension-map keys). See Reason Keys Reference. |
trigger_class | e.g. post, taxonomy, presentation, async, system |
trigger_kind | Trigger subtype classification (when present). |
trigger_event | Specific event key. |
trigger_post_id | Triggering post ID (0 for non-post triggers). |
trigger_post_type | Triggering post type (empty for non-post triggers). |
groups | Matched configured post type keys (user-facing invalidation groups). |
fanout_mode | Multilingual fanout policy (same_language or all_languages). |
fanout_languages | Resolved target languages. |
multilingual, resolver, resolver_model | Present when a language resolver is active. |
intent, resolution, fallback, fallback_reason | Execution metadata when provided by the current invalidation flow. |
urls | Final authoritative emitted URL set for downstream intake. |
trusted_urls | Optional subset of URLs that are safe to warm without checks. Only set for internal (deterministic) events. |
trusted_source | Emission hook identifier (ekesto_ci_invalidation_urls). |
External cache-plugin signals are always untrusted – meaning they are checked for sitemap membership.
Use case: Listen for invalidated URLs
React to invalidation events for logging, external sync, or custom warmup.
add_action('ekesto_ci_invalidation_urls', function (array $urls, array $context): void {
// foreach ($urls as $url) {
// Process each invalidated URL.
// }
}, 10, 2);Filters
Extension points are the supported integration surface for Cache Invalidator. All major decision points expose WordPress hooks and filters (customization hooks).
Each filter provides example Use Cases that are extended based on real-life scenarios. Filters can be used creatively.
There is no public PHP API contract beyond these hooks.
Overview
Add extra URLs to the invalidation emission set before the primary integration point action fires.
ekesto_ci_relationship_meta_keys
Extends the relationship resolution engine when relationships are stored outside automatically detected systems.
Pick which pages should refresh when a specific post changes.
Pick which pages should refresh when a sidebar or widget area changes.
ekesto_ci_widget_template_mappings
Tell the system which widget is displayed on which page template, so the right pages refresh when a widget changes.
ekesto_ci_meta_change_post_types
Refresh pages when specific custom fields are updated silently (e.g. by imports or integrations) without a normal post save.
Refresh the entire site whenever a specific custom hook fires.
Tell the system which WordPress option changes should count as a reason to refresh.
Pick exactly which pages should refresh when one of those option changes happens.
Add extra pages that should refresh alongside archive pages when content changes.
Directly add or remove specific archive URLs from the refresh list (e.g. custom archives or feeds).
ekesto_ci_forms_scan_post_types
Choose which post types are searched when looking for pages that embed a form.
Filter: ekesto_ci_additional_urls
Add extra URLs to the invalidation emission set before the ekesto_ci_invalidation_urls action fires. Returned URLs are merged with core URLs, then trimmed and deduplicated.
Signature
add_filter(
'ekesto_ci_additional_urls',
function (array $urls, array $context): array,
10,
2
);Parameters
| Parameter | Type | Description |
|---|---|---|
$urls | array | Base deterministic URL set before extension |
$context | array | Pre-emission invalidation metadata (see table below) |
$context reference
| Key | Description |
|---|---|
source | Cache adapter key. |
source_label | Human-readable cache adapter name. |
reason | Invalidation reason key from emission context (built-in runtime keys + custom extension-map keys). See Reason Keys Reference. |
trigger_class | Event family (post, taxonomy, comment, meta, timed, form, presentation, async). |
trigger_kind | Trigger subtype classification (when present). |
trigger_event | Specific event key. |
trigger_post_id | Triggering post ID (0 for non-post triggers). |
trigger_post_type | Triggering post type (empty for non-post triggers). |
groups | Matched configured post type keys (user-facing invalidation groups). |
fanout_mode | Multilingual fanout policy (same_language or all_languages). |
fanout_languages | Resolved target languages. |
multilingual, resolver, resolver_model | Present when a language resolver is active. |
intent, resolution, fallback, fallback_reason | Execution metadata when provided by the current invalidation flow. |
trusted_urls | Optional trusted URL subset when upstream flow already set it. Values added by this filter are treated as untrusted. |
Behavior note: Return only additional URLs to merge into the emission set.
Returns
| Type | Description |
|---|---|
array | Additional URLs to merge into the emission URL set |
Use case: Always include a specific page
Include a URL in every invalidation emission.
add_filter('ekesto_ci_additional_urls', function (array $urls, array $context): array {
$urls[] = home_url('/pricing/');
return $urls;
}, 10, 2);Use case: Include a page by post ID
Resolve a page’s permalink from its post ID and add it to the emission set.
add_filter('ekesto_ci_additional_urls', function (array $urls, array $context): array {
$campaignPageId = 42;
$campaignUrl = get_permalink($campaignPageId);
if (is_string($campaignUrl) && $campaignUrl !== '') {
$urls[] = $campaignUrl;
}
return $urls;
}, 10, 2);Use case: Add URLs conditionally based on trigger
Include additional URLs only when a specific trigger fires.
add_filter('ekesto_ci_additional_urls', function (array $urls, array $context): array {
if (($context['reason'] ?? '') === 'form_change') {
$urls[] = home_url('/thank-you/');
}
return $urls;
}, 10, 2);Filter: ekesto_ci_relationship_meta_keys
Extends the relationship resolution engine when relationships are stored outside automatically detected systems.
Most sites do not need this filter.
Relationships handled automatically include:
- ACF
relationshipandpost_objectfields - WooCommerce product relationships (variations, grouped products, upsells, cross-sells) and core page references
- Standard WordPress parent/child relationships
Use this filter only when relationships are stored in:
- Custom meta structures
- Custom option references
- Non-ACF relationship fields
- External tables
- Custom data models
This filter extends relationship discovery. It does not replace built-in detection. Use this filter only when relationships are stored in custom fields, options, hierarchy, or custom tables.
Signature
add_filter(
'ekesto_ci_relationship_meta_keys',
function (array $definitions, string $hostPostType, string $relatedPostType): array,
10,
3
);Parameters
| Parameter | Type | Description |
|---|---|---|
$definitions | array | Existing strategy definitions for the current host/related pair (includes ACF + WooCommerce defaults when available). |
$hostPostType | string | Host post type that displays/references related content. |
$relatedPostType | string | Post type that changed. |
Strategy Definitions Reference
| Kind | Required keys in $definitions[] | Lookup method | Typical use case |
|---|---|---|---|
meta_exact | kind, meta_key | Post meta value equals the related post ID. | Single-value relationship fields. |
meta_serialized | kind, meta_key | Serialized post meta contains the related post ID. | Multi-value relationship fields. |
post_parent | kind | Changed post’s post_parent points to the host. | Hierarchical content. |
option_id | kind, option_key | WordPress option stores a post ID. | Site-level option references. |
callback | kind, callback | Custom callback returns host post IDs. | Custom/external storage logic. |
Strategies may be combined. If multiple definitions match, all
resolved host posts are merged before invalidation.
Returns
| Type | Description |
|---|---|
array | Final strategy definitions used for relationship lookup. Invalid entries are ignored at runtime; non-callback strategies are deduplicated. |
Use case: Event hosting venue (manual meta field)
When a venue changes, refresh events that take place there.
Setup:
Host post type: event
Related post type: venue
Relationship storage: venue_id (exact meta)
add_filter(
'ekesto_ci_relationship_meta_keys',
function (array $definitions, string $hostPostType, string $relatedPostType): array {
if ($hostPostType === 'event' && $relatedPostType === 'venue') {
$definitions[] = [
'kind' => 'meta_exact',
'meta_key' => 'venue_id'
];
}
return $definitions;
},
10,
3
);Typical real setup:
- Events CPT
- Venues CPT
- Custom plugin storing venue ID in postmeta
Result: Updating a venue refreshes related event pages.
Use case: WooCommerce custom product bundles
WooCommerce standard product relationships are detected automatically (variations, upsells, cross-sells, grouped products). This example shows how to extend relationship detection when a plugin or custom implementation stores additional product connections.
When a bundled product changes, bundle product pages referencing it should refresh.
Setup:
Host post type: product
Related post type: product
Relationship storage: (serialized meta)bundle_product_ids
add_filter(
'ekesto_ci_relationship_meta_keys',
function (array $definitions, string $hostPostType, string $relatedPostType): array {
if ($hostPostType === 'product' && $relatedPostType === 'product') {
$definitions[] = [
'kind' => 'meta_serialized',
'meta_key' => 'bundle_product_ids'
];
}
return $definitions;
},
10,
3
);Typical real setup:
- WooCommerce store
- Product bundle plugin or custom bundle implementation
- Bundle products referencing multiple included products via custom meta
Result:
When a bundled product is updated (price, stock, content), bundle product pages that include it are automatically invalidated and refreshed. This avoids stale bundle pricing or availability without requiring full cache clears.
Use case: Course hosting lessons (parent/child CPT)
When a lesson changes, refresh the course page that hosts it.
Setup:
Host post type: course
Related post type: lesson
Relationship storage: lesson (parent post) → course
add_filter(
'ekesto_ci_relationship_meta_keys',
function (array $definitions, string $hostPostType, string $relatedPostType): array {
if ($hostPostType === 'course' && $relatedPostType === 'lesson') {
$definitions[] = [
'kind' => 'post_parent'
];
}
return $definitions;
},
10,
3
);Typical real setup:
- Course CPT
- Lesson CPT
- Lessons assigned as children of course
Result:
Updating a lesson refreshes the course page.
Use case: LMS plugin default instructor setting (options)
When an instructor profile changes, refresh courses that use that instructor as the default.
Setup:
Host post type: course
Related post type: instructor
Relationship storage: LMS option stores instructor ID
Example plugin option:lms_default_instructor = 87
add_filter(
'ekesto_ci_relationship_meta_keys',
function (array $definitions, string $hostPostType, string $relatedPostType): array {
if ($hostPostType === 'course' && $relatedPostType === 'instructor') {
$definitions[] = [
'kind' => 'option_id',
'option_key' => 'lms_default_instructor'
];
}
return $definitions;
},
10,
3
);Typical real setup:
- LMS plugin
- Instructor CPT
- Courses using fallback instructor from settings
Result:
Updating the instructor refreshes affected courses.
Use case: Event hosting speakers from custom relationship table (advanced)
When a speaker changes, refresh events hosting speakers from a custom relationship table.
Setup:
Host post type: event
Related post type: speaker
Relationship storage: mb_relationships table (Meta Box relationships)
add_filter(
'ekesto_ci_relationship_meta_keys',
function (array $definitions, string $hostPostType, string $relatedPostType): array {
if ($hostPostType !== 'event' || $relatedPostType !== 'speaker') {
return $definitions;
}
$definitions[] = [
'kind' => 'callback',
'callback' => static function (string $hostPt, string $relatedPt, int $relatedId): array {
global $wpdb;
$type = 'events_to_speakers';
$table = $wpdb->prefix . 'mb_relationships';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$hostIds = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT `from` FROM {$table} WHERE `type` = %s AND `to` = %d",
$type,
$relatedId
));
return is_array($hostIds) ? array_map('intval', $hostIds) : [];
},
];
return $definitions;
},
10,
3
);Typical real setup:
- Events CPT
- Speakers CPT
- Relationship stored in custom table
Result:
Updating a speaker refreshes all connected events.
Filter: ekesto_ci_post_selectors
Use this filter when a post change should invalidate pages beyond the configured Post Type targets.
Signature
add_filter(
'ekesto_ci_post_selectors',
function (array $selectors, array $context): array,
10,
2
);Parameters
| Parameter | Type | Description |
|---|---|---|
$selectors | array | Selector entries (empty by default) |
$context | array | Trigger metadata (see table below). |
$context reference
| Key | Description |
|---|---|
trigger_post_id | ID of the changed post. |
trigger_post_type | Post type of the changed post. |
reason | Invalidation reason key. See list below. |
groups | Enabled configured host post types matched for this trigger. |
is_publicly_queryable | Whether the triggering post type is publicly queryable. |
builder_key | Detected builder identifier |
builder_confidence | Builder detection confidence |
multilingual | Present if multilingual context exists |
language | Trigger language (optional) |
languages | Resolved language list (optional) |
resolver | Active multilingual resolver (optional) |
resolver_model | Resolver model (optional) |
$reason keys for this filter
first_publish, scheduled_publish, update_published, deleted, untrashed.
See Reason Keys Reference for the canonical cross-hook map and custom key rules.
Returns
| Type | Description |
|---|---|
array | Selector entries with allowed types: post_id, post_type, or all |
When to use
Typical cases:
- Non-public CPTs affecting frontend pages
- Builder templates affecting multiple pages
- Relationship-driven content structures
- Language-specific targeting
- Conditional targeting based on builder detection
Use case: Target specific pages when a CPT changes
Invalidate specific frontend pages when a custom post type changes.
add_filter('ekesto_ci_post_selectors', function (
array $selectors,
array $context
): array {
if (($context['trigger_post_type'] ?? '') !== 'example_post_type') {
return $selectors;
}
/*
* Example 1: Individual selectors (simple cases)
*/
/*
return [
['type' => 'post_id', 'value' => 101],
['type' => 'post_id', 'value' => 205],
];
*/
/*
* Example 2: Dynamic selector generation (recommended for multiple IDs)
*/
$ids = [101, 205, 309];
return array_map(
static fn (int $id): array => [
'type' => 'post_id',
'value' => $id,
],
$ids
);
}, 10, 2);Use case: Target all pages when a CPT changes
Invalidate all page post types when a custom post type changes.
add_filter('ekesto_ci_post_selectors', function (
array $selectors,
array $context
): array {
if (($context['trigger_post_type'] ?? '') !== 'example_post_type') {
return $selectors;
}
return [
['type' => 'post_type', 'value' => 'page'],
];
}, 10, 2);Use case: Global invalidation when a CPT changes
Force global invalidation when a custom post type changes.
add_filter('ekesto_ci_post_selectors', function (
array $selectors,
array $context
): array {
if (($context['trigger_post_type'] ?? '') !== 'example_post_type') {
return $selectors;
}
return [
['type' => 'all'],
];
}, 10, 2);Selector Behavior
- Selectors add pages to the refresh list
all= refresh the entire site (stops further processing)- Supported types:
post_id,post_type,all - Invalid selectors are ignored
- If nothing valid is returned, default behavior continues
Filter: ekesto_ci_widget_selectors
Provide selectors that define invalidation intent.
If no valid selectors are returned, default widget invalidation behavior applies based on the widget fallback setting.
If your cache plugin clears everything on widget changes, that takes precedence over targeted invalidation.
Signature
add_filter(
'ekesto_ci_widget_selectors',
function (array $selectors, mixed $oldValue, mixed $newValue, array $changedSidebars): array,
10,
4
);Parameters
| Parameter | Type | Description |
|---|---|---|
$selectors | array | Selector entries (empty by default) |
$oldValue | mixed | Previous sidebars_widgets option value |
$newValue | mixed | New sidebars_widgets option value |
$changedSidebars | array | Sidebar IDs that actually changed |
Returns
| Type | Description |
|---|---|
array | Selector entries with allowed types: post_id, post_type, or all |
Use case: Target pages when specific sidebars change
Target pages only when certain sidebars are modified.
add_filter('ekesto_ci_widget_selectors', function (
array $selectors,
mixed $oldValue,
mixed $newValue,
array $changedSidebars
): array {
if (in_array('example-sidebar-header', $changedSidebars, true)) {
return [
['type' => 'post_id', 'value' => 12],
['type' => 'post_id', 'value' => 45],
];
}
if (in_array('example-sidebar-footer', $changedSidebars, true)) {
return [
['type' => 'post_type', 'value' => 'page'],
];
}
return $selectors;
}, 10, 4);Use case: Global invalidation for critical widget areas
Trigger global invalidation when important sidebars change.
add_filter('ekesto_ci_widget_selectors', function (
array $selectors,
mixed $oldValue,
mixed $newValue,
array $changedSidebars
): array {
if (in_array('example-sidebar-global', $changedSidebars, true)) {
return [
['type' => 'all'],
];
}
return $selectors;
}, 10, 4);Selector Behavior
- Selectors add pages to the refresh list
all= refresh the entire site (stops further processing)- Supported types:
post_id,post_type,all - Invalid selectors are ignored
- If nothing valid is returned, default behavior continues
Sidebar IDs match WordPress sidebar registration IDs (e.g. sidebar-1, footer-1, or custom IDs from register_sidebar()).
Filter: ekesto_ci_widget_template_mappings
Map changed widget sidebars to Classic WordPress page templates. When a mapped sidebar changes, published posts assigned to those templates are added to targeted invalidation via _wp_page_template postmeta lookup.
If your cache plugin clears everything on widget changes, that takes precedence over targeted invalidation.
Signature
add_filter(
'ekesto_ci_widget_template_mappings',
function (array $mappings, array $changedElements, array $context): array,
10,
3
);Parameters
| Parameter | Type | Description |
|---|---|---|
$mappings | array | Sidebar ID to template slug mapping (empty by default) |
$changedElements | array | Sidebar IDs that changed in this widget event (for example sidebar-1, footer-1) |
$context | array | Trigger context: trigger_class, trigger_event |
Returns
| Type | Description |
|---|---|
array | Sidebar ID to template slug mapping |
Use case: Header widget updates pages using specific templates
When your header widget area changes, invalidate pages that use header-heavy Classic templates.
add_filter('ekesto_ci_widget_template_mappings', function (
array $mappings,
array $changedElements,
array $context
): array {
$mappings['sidebar-1'] = ['templates/homepage.php', 'templates/contact.php'];
$mappings['sidebar-footer'] = ['templates/full-width.php'];
return $mappings;
}, 10, 3);Use case: Footer widget updates Event pages using one template
When the Events footer widget area changes, invalidate only Event posts assigned to the Event template.
add_filter('ekesto_ci_widget_template_mappings', function (array $mappings): array {
$mappings['sidebar-events'] = ['templates/event-layout.php'];
return $mappings;
});Template values must match _wp_page_template postmeta exactly (no theme hierarchy inference). The value 'default' matches pages without an explicit template. Resolved IDs are merged with ekesto_ci_widget_selectors TARGETED IDs. When widget selectors return ['type' => 'all'], widget-template resolution is skipped.
Filter: ekesto_ci_meta_change_post_types
Define which post meta changes should trigger invalidation when no post update occurs.
Use this when plugins, imports, or integrations update important frontend data via post meta without triggering a post update.
Supports exact meta key matching and wildcard prefix patterns (*).
Signature
add_filter(
'ekesto_ci_meta_change_post_types',
function (array $map): array
);Parameters
| Parameter | Type | Description |
|---|---|---|
$map | array | Post type → meta key patterns mapping (empty by default) |
Returns
| Type | Description |
|---|---|
array | Post type → meta key patterns mapping |
When you typically need this
Most sites do not need this filter. Cache Invalidator already detects normal content updates automatically.
Use this only when important frontend data changes via:
- Import plugins (WP All Import, feeds, sync tools)
- External integrations (ERP, CRM, booking systems)
- Custom automation or cron jobs
- Agency-built meta toggles affecting visibility
Behavior notes
- Only published posts trigger invalidation.
- Draft and pending posts are ignored.
- Wildcards match prefixes only (
meta_prefix_*). - Only configured keys trigger invalidation.
Use case: Product price updates from imports
Refresh product pages when prices are updated by imports or integrations.
add_filter('ekesto_ci_meta_change_post_types', function (array $map): array {
$map['product'] = [
'_price',
'_sale_price',
];
return $map;
});Typical setup:
- WooCommerce products
- Prices updated via import or sync
- No manual post edit
Result:
Updating a product price refreshes the product page.
Use case: Wildcard matching for plugin meta
Use wildcards when plugins store multiple related meta keys.
add_filter('ekesto_ci_meta_change_post_types', function (array $map): array {
$map['page'] = [
'_elementor_*',
];
return $map;
});Typical setup:
- Builders or plugins storing many related meta fields
- All keys share a prefix
Result:
Any matching meta update triggers invalidation.
Filter: ekesto_ci_global_triggers
Trigger a full site refresh from any custom hook.
When one of your mapped hooks runs, WP Cache Autopilot skips all targeting logic and immediately refreshes the entire site.
Mapped values create custom reason_key values. See Custom reason keys (extension maps).
Warning: This always refreshes the entire site. Use it only when you cannot target specific pages.
Signature
add_filter(
'ekesto_ci_global_triggers',
function (array $map): array
);Parameters
| Parameter | Type | Description |
|---|---|---|
$map | array | Hook name to reason key mapping (empty by default). Both values sanitized via sanitize_key(). |
Returns
| Type | Description |
|---|---|
array | Hook-to-reason mapping |
How this fits into the system
| System | Purpose |
|---|---|
ekesto_ci_option_triggers | Define which options trigger invalidation |
ekesto_ci_option_selectors | Define what gets refreshed |
ekesto_ci_global_triggers | Always refresh the entire site |
Use case: Refresh the entire site after settings are saved
Trigger direct global invalidation when theme options or WooCommerce settings are saved.
add_filter('ekesto_ci_global_triggers', function (array $map): array {
$map['my_theme_options_saved'] = 'theme_options_change';
$map['woocommerce_settings_saved'] = 'woocommerce_settings';
return $map;
});Filter: ekesto_ci_option_triggers
Register option changes that should trigger cache updates.
Only the options you list here are observed. Everything else is ignored.
Matching is exact — the option key must match exactly.
Signature
add_filter(
'ekesto_ci_option_triggers',
function (array $map): array
);Map contract
| Parameter | Type | Description |
|---|---|---|
$map | array | Associative mapping in option_key => reason_key format. |
When to use
Use this when an option change should refresh part of your site.
- Branding or theme settings
- Plugin settings affecting specific content
- Important toggles that may require a full refresh
Use case: React to brand settings changes
When your brand settings option changes, invalidate all published pages without touching unrelated post types.
add_filter('ekesto_ci_option_triggers', function (array $map): array {
$map['myplugin_brand_settings'] = 'brand_settings_change';
return $map;
});Option triggers only define when something happens.
They do nothing until you define what should be refreshed via ekesto_ci_option_selectors filter.
Filter: ekesto_ci_option_selectors
Define what should be refreshed when an option changes.
You can target:
- specific pages (
post_id) - entire post types (
post_type) - or the whole site (
all)
Option-trigger selectors are strict and explicit: only selector-defined targets are used. Only what you select is refreshed. Archives are not added automatically.
Signature
add_filter(
'ekesto_ci_option_selectors',
function (array $selectors, array $context): array,
10,
2
);Parameters
| Parameter | Type | Description |
|---|---|---|
$selectors | array | Selector entries (internal defaults + extension-provided values). |
$context | array | Option trigger metadata (see table below). |
$context keys
| Key | Description |
|---|---|
trigger_class | Always option for this filter. |
trigger_option | Normalized option key that triggered evaluation. |
reason | Normalized reason key from ekesto_ci_option_triggers. |
Returns
| Type | Description |
|---|---|
array | Selector entries with allowed types: post_id, post_type, or all. |
When to use
Use this to control exactly which pages are affected by an option change.
Use case: Refresh all pages after brand changes
When your brand settings option changes, invalidate all published pages without touching unrelated post types.
add_filter('ekesto_ci_option_selectors', function (array $selectors, array $context): array {
if (($context['trigger_option'] ?? '') !== 'myplugin_brand_settings') {
return $selectors;
}
return [
['type' => 'post_type', 'value' => 'page'],
];
}, 10, 2);How selectors work
- Selectors are additive — each one adds more pages
all= refresh everything (and stops further processing)- Invalid selectors are ignored
- No selectors = nothing happens
If an option is not registered, it is completely ignored. To participate, an option must be explicitly registered in ekesto_ci_option_triggers.
Filter: ekesto_ci_multilingual_fanout_mode
Override the computed multilingual fanout mode per trigger context.
Signature
add_filter(
'ekesto_ci_multilingual_fanout_mode',
function (string $mode, array $context): string,
10,
2
);Parameters
| Parameter | Type | Description |
|---|---|---|
$mode | string | Current fanout mode (same_language or all_languages) |
$context | array | Trigger context metadata |
$context reference
| Key | Description |
|---|---|
trigger_class | Event family (for example post, timed, comment, presentation, async). |
trigger_kind | Trigger subtype used by fanout policy classification. |
trigger_event | Specific event key for the current trigger. |
reason | Invalidation reason key. See Reason Keys Reference. |
trigger_post_id | Triggering post ID (0 for non-post triggers). |
trigger_post_type | Triggering post type (empty for non-post triggers). |
multilingual_fanout_post_triggers_enabled | Whether post-like triggers default to all_languages. |
multilingual_fanout_non_post_triggers_enabled | Whether non-post triggers default to all_languages. |
resolver_available | Whether a multilingual resolver is active. |
trigger_language_available | Whether a trigger language is known for this event. |
trigger_language | Trigger language code when available (optional). |
intent, resolution, fallback, fallback_reason | Optional execution metadata present in some flows. |
Reason keys for this filter
first_publish, scheduled_publish, update_published, deleted, untrashed, taxonomy_term_assignment, taxonomy_term_edited, taxonomy_term_deleted, comment_change, meta_change, form_change, form_change_fallback, timed_invalidation, customizer_change, menu_change, menu_location_change, switch_theme, widgets_change, reading_settings_change, permalink_settings_change, navigation_change, template_change, synced_pattern_change, synced_pattern_change_fallback, elementor_template_change, elementor_template_change_fallback, elementor_component_change, elementor_component_change_fallback, async_deep_invalidation.
Custom reason keys from extension maps can also appear.
See Reason Keys Reference for the canonical cross-hook map and custom key rules.
Returns
| Type | Description |
|---|---|
string | Fanout mode (same_language or all_languages) |
Use case: Force all-languages fanout for timed invalidation
Ensure timed invalidation always includes all language variants.
add_filter('ekesto_ci_multilingual_fanout_mode', function (string $mode, array $context): string {
if (($context['trigger_class'] ?? '') === 'timed') {
return 'all_languages';
}
return $mode;
}, 10, 2);Filter: ekesto_ci_archive_selectors
Provide selectors that define invalidation intent.
This filter allows adding additional archive invalidation targets when posts or taxonomy terms change. Selectors define intent and scope only. Archive URL resolution remains internal.
If no valid selectors are returned, default archive resolution behavior applies.
Signature
add_filter(
'ekesto_ci_archive_selectors',
function (array $selectors, array $context): array,
10,
2
);Parameters
| Parameter | Type | Description |
|---|---|---|
$selectors | array | Selector entries (empty by default) |
$context | array | Archive resolution context (see table below) |
$context reference
| Key | Description |
|---|---|
source | Resolver source: post or term. |
post_id | Changed post ID (present when source=post). |
post_type | Changed post type slug (present when source=post). |
taxonomy | Changed taxonomy slug (present when source=term). |
term_id | Changed term ID (present when source=term). |
include_taxonomy_archives | Whether taxonomy archives are included |
Returns
| Type | Description |
|---|---|
array | Selector entries with allowed types: post_id, post_type, or all |
When to use
Use this filter when archive invalidation should include additional content beyond the normal archive resolution.
Typical cases:
- Product changes affecting landing pages
- Taxonomy changes affecting special pages
- Builder-driven archive layouts
- Custom archive relationships
Use case: Target additional content when a CPT changes
Add additional invalidation targets when a custom post type changes.
add_filter('ekesto_ci_archive_selectors', function (
array $selectors,
array $context
): array {
if (($context['source'] ?? '') !== 'post') {
return $selectors;
}
if (($context['post_type'] ?? '') !== 'product') {
return $selectors;
}
/*
* Example 1: Individual selectors
*/
/*
return [
['type' => 'post_id', 'value' => 42], // specific post
['type' => 'post_type', 'value' => 'page'], // all posts of type 'page'
];
*/
/*
* Example 2: Dynamic selector generation
*/
$ids = [42, 84, 126];
return array_map(
static fn (int $id): array => [
'type' => 'post_id',
'value' => $id,
],
$ids
);
}, 10, 2);Use case: Global archive invalidation for taxonomy changes
Force global invalidation when important taxonomy structures change.
add_filter('ekesto_ci_archive_selectors', function (
array $selectors,
array $context
): array {
if (($context['source'] ?? '') !== 'term') {
return $selectors;
}
if (($context['taxonomy'] ?? '') !== 'product_cat') {
return $selectors;
}
return [
['type' => 'all'],
];
}, 10, 2);Selector Behavior
- Selectors add pages to the refresh list
all= refresh the entire site (stops further processing)- Supported types:
post_id,post_type,all - Invalid selectors are ignored
- If nothing valid is returned, default behavior continues
This filter adds archive targets. It does not replace default archive resolution unless GLOBAL intent is returned.
Filter: ekesto_ci_archive_urls
Filter resolved archive URLs directly. Use when explicit URL-level control is needed. Prefer ekesto_ci_archive_selectors when possible.
Signature
add_filter(
'ekesto_ci_archive_urls',
function (array $urls, array $context): array,
10,
2
);Parameters
| Parameter | Type | Description |
|---|---|---|
$urls | array | Resolved archive URLs (post type + taxonomy archives) |
$context | array | Same context as ekesto_ci_archive_selectors |
$context reference
| Key | Description |
|---|---|
source | Resolver source: post or term. |
post_id | Changed post ID (present when source=post). |
post_type | Changed post type slug (present when source=post). |
taxonomy | Changed taxonomy slug (present when source=term). |
term_id | Changed term ID (present when source=term). |
include_taxonomy_archives | Whether taxonomy archive URLs are included in archive resolution for this run (boolean). |
Returns
| Type | Description |
|---|---|
array | Modified URL list |
Use case: Add a custom archive URL
Include a custom archive page URL or resolve one from a post ID.
add_filter('ekesto_ci_archive_urls', function (array $urls, array $context): array {
$urls[] = home_url('/resources/archive/');
$archivePageId = 42;
$archivePageUrl = get_permalink($archivePageId);
if (is_string($archivePageUrl) && $archivePageUrl !== '') {
$urls[] = $archivePageUrl;
}
return $urls;
}, 10, 2);Use case: Add author archive URL
Include the post author’s archive page during post-driven invalidation.
add_filter('ekesto_ci_archive_urls', function (array $urls, array $context): array {
if (($context['source'] ?? '') === 'post') {
$postId = absint($context['post_id'] ?? 0);
if ($postId > 0) {
$authorId = (int) get_post_field('post_author', $postId);
if ($authorId > 0) {
$authorUrl = get_author_posts_url($authorId);
if (is_string($authorUrl) && $authorUrl !== '') {
$urls[] = $authorUrl;
}
}
}
}
return $urls;
}, 10, 2);Use case: Add RSS feed URL
Include the site RSS feed in archive invalidation targets.
add_filter('ekesto_ci_archive_urls', function (array $urls, array $context): array {
$siteFeedUrl = get_feed_link('rss2');
if (is_string($siteFeedUrl) && $siteFeedUrl !== '') {
$urls[] = $siteFeedUrl;
}
return $urls;
}, 10, 2);Filter: ekesto_ci_forms_scan_post_types
Filter which post types are scanned for form shortcode and block references. Default: all public post types.
Signature
add_filter(
'ekesto_ci_forms_scan_post_types',
function (array $postTypes): array
);Parameters
| Parameter | Type | Description |
|---|---|---|
$postTypes | array | Default public post types |
Returns
| Type | Description |
|---|---|
array | Modified post type list |
Use case: Add a custom landing page CPT
Include a custom post type that embeds forms in its content.
add_filter('ekesto_ci_forms_scan_post_types', function (array $postTypes): array {
$postTypes[] = 'landing_page';
return $postTypes;
});Reason Keys Reference
A reason tells you why cache invalidation happened.
The trigger_event may provide a more specific label for the same event.
Think of reason as the cause of a cache refresh — useful for debugging, logging, and custom logic.
Built-in reason keys (grouped by trigger type)
These keys appear in the reason field across filters and actions and help you understand what triggered the cache refresh.
- Post changes:
first_publish,scheduled_publish,update_published,deleted,untrashed - Taxonomy changes:
taxonomy_term_assignment,taxonomy_term_edited,taxonomy_term_deleted - Comment:
comment_change - Custom fields:
meta_change - Forms:
form_change,form_change_fallback - Scheduled:
timed_invalidation - Site settings & layout:
customizer_change,menu_change,menu_location_change,switch_theme,widgets_change,reading_settings_change,permalink_settings_change - Templates & builders:
navigation_change,template_change,synced_pattern_change,synced_pattern_change_fallback,elementor_template_change,elementor_template_change_fallback,elementor_component_change,elementor_component_change_fallback - Background processing:
async_deep_invalidation
Context matrix
| Hook | reason in context | Notes |
|---|---|---|
ekesto_ci_invalidation_urls | Yes | Full runtime context — includes all available reason values. |
ekesto_ci_additional_urls | Yes | Runs before final output — urls and trusted_source are not available yet. |
ekesto_ci_multilingual_fanout_mode | Yes | Runtime trigger context used for fanout policy. |
ekesto_ci_post_selectors | Yes | Post trigger context (subset of full reason space). |
ekesto_ci_archive_selectors | No | Does not use reason — relies on archive context (post_id, term_id, etc.). |
ekesto_ci_archive_urls | No | Same context shape as ekesto_ci_archive_selectors. |
Custom reason keys (extension maps)
You can define your own reason values using:
ekesto_ci_global_triggers(map WordPress hook name →reason_key)ekesto_ci_option_triggers(map option key →reason_key)ekesto_ci_option_selectors(map option trigger context → selector entries)
Custom keys are sanitized using sanitize_key(). Use lowercase snake_case for consistency.
Advanced extension points
Filter: ekesto_ci_language_resolver
Replace the internal multilingual resolver with a custom implementation. Only for language systems not supported by built-in detection (WPML, Polylang, TranslatePress).
Signature
add_filter(
'ekesto_ci_language_resolver',
function (?LanguageResolverInterface $resolver): ?LanguageResolverInterface
);Parameters
| Parameter | Type | Description |
|---|---|---|
$resolver | LanguageResolverInterface|null | Auto-detected resolver instance, or null if none detected |
Returns
| Type | Description |
|---|---|
LanguageResolverInterface|null | Resolver instance or null |
Most installations should not use this filter. Custom resolvers should implement both LanguageResolverInterface and ResolverMetadataInterface.