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

TermDefinition
InvalidationDeterministic decision of which URLs must be refreshed based on a detected change
PurgeRemoval of cache entries executed by the active cache plugin
WarmupRebuilding cache by issuing HTTP requests to previously purged URLs
ResolutionProcess of determining affected URLs from a trigger (includes relationships, archives, and structural usage)
TriggerSystem event indicating a potential change (e.g., post update, option change, structural modification)
Targets / Target URLsFinal 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 propagationExpansion of targets through content relationships (host ↔ related content model)
Cache adapterIntegration layer translating purge instructions into cache-plugin-specific operations
Targeted warmupExecution mode where only resolved target URLs are warmed after invalidation
Full warmupExecution mode where all known URLs (e.g., sitemap set) are warmed independent of a specific change
Async deep invalidationDeferred background resolution of complex target sets (e.g., relationship graphs) via WP-Cron to avoid blocking request lifecycle
Global invalidationStrategy where all cache is purged due to uncertainty or system-wide change
Global fallbackSafety 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 typeContent type whose pages are being invalidated
Related post typeContent 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
ParameterTypeDescription
$urlsarrayFlat list of absolute URLs to warm.
Empty ([]) means full/global invalidation.
$contextarrayInvalidation event metadata (see table below)

context.urls is always present.
If no URLs exist for a targeted event, nothing is emitted.

$context keys
KeyDescription
sourceCache adapter key.
source_labelHuman-readable cache adapter name.
reasonInvalidation reason key from emission context (built-in runtime keys + custom extension-map keys). See Reason Keys Reference.
trigger_classe.g. post, taxonomy, presentation, async, system
trigger_kindTrigger subtype classification (when present).
trigger_eventSpecific event key.
trigger_post_idTriggering post ID (0 for non-post triggers).
trigger_post_typeTriggering post type (empty for non-post triggers).
groupsMatched configured post type keys (user-facing invalidation groups).
fanout_modeMultilingual fanout policy (same_language or all_languages).
fanout_languagesResolved target languages.
multilingual, resolver, resolver_modelPresent when a language resolver is active.
intent, resolution, fallback, fallback_reasonExecution metadata when provided by the current invalidation flow.
urlsFinal authoritative emitted URL set for downstream intake.
trusted_urlsOptional subset of URLs that are safe to warm without checks.
Only set for internal (deterministic) events.
trusted_sourceEmission 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

ekesto_ci_additional_urls

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.

ekesto_ci_post_selectors

Pick which pages should refresh when a specific post changes.

ekesto_ci_widget_selectors

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.

ekesto_ci_global_triggers

Refresh the entire site whenever a specific custom hook fires.

ekesto_ci_option_triggers

Tell the system which WordPress option changes should count as a reason to refresh.

ekesto_ci_option_selectors

Pick exactly which pages should refresh when one of those option changes happens.

ekesto_ci_archive_selectors

Add extra pages that should refresh alongside archive pages when content changes.

ekesto_ci_archive_urls

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
ParameterTypeDescription
$urlsarrayBase deterministic URL set before extension
$contextarrayPre-emission invalidation metadata (see table below)
$context reference
KeyDescription
sourceCache adapter key.
source_labelHuman-readable cache adapter name.
reasonInvalidation reason key from emission context (built-in runtime keys + custom extension-map keys). See Reason Keys Reference.
trigger_classEvent family (post, taxonomy, comment, meta, timed, form, presentation, async).
trigger_kindTrigger subtype classification (when present).
trigger_eventSpecific event key.
trigger_post_idTriggering post ID (0 for non-post triggers).
trigger_post_typeTriggering post type (empty for non-post triggers).
groupsMatched configured post type keys (user-facing invalidation groups).
fanout_modeMultilingual fanout policy (same_language or all_languages).
fanout_languagesResolved target languages.
multilingual, resolver, resolver_modelPresent when a language resolver is active.
intent, resolution, fallback, fallback_reasonExecution metadata when provided by the current invalidation flow.
trusted_urlsOptional 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
TypeDescription
arrayAdditional 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 relationship and post_object fields
  • 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
ParameterTypeDescription
$definitionsarrayExisting strategy definitions for the current host/related pair (includes ACF + WooCommerce defaults when available).
$hostPostTypestringHost post type that displays/references related content.
$relatedPostTypestringPost type that changed.
Strategy Definitions Reference
KindRequired keys in $definitions[]Lookup methodTypical use case
meta_exactkind, meta_keyPost meta value equals the related post ID.Single-value relationship fields.
meta_serializedkind, meta_keySerialized post meta contains the related post ID.Multi-value relationship fields.
post_parentkindChanged post’s post_parent points to the host.Hierarchical content.
option_idkind, option_keyWordPress option stores a post ID.Site-level option references.
callbackkind, callbackCustom 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
TypeDescription
arrayFinal 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: bundle_product_ids (serialized meta)

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
ParameterTypeDescription
$selectorsarraySelector entries (empty by default)
$contextarrayTrigger metadata (see table below).
$context reference
KeyDescription
trigger_post_idID of the changed post.
trigger_post_typePost type of the changed post.
reasonInvalidation reason key. See list below.
groupsEnabled configured host post types matched for this trigger.
is_publicly_queryableWhether the triggering post type is publicly queryable.
builder_keyDetected builder identifier
builder_confidenceBuilder detection confidence
multilingualPresent if multilingual context exists
languageTrigger language (optional)
languagesResolved language list (optional)
resolverActive multilingual resolver (optional)
resolver_modelResolver 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
TypeDescription
arraySelector 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
ParameterTypeDescription
$selectorsarraySelector entries (empty by default)
$oldValuemixedPrevious sidebars_widgets option value
$newValuemixedNew sidebars_widgets option value
$changedSidebarsarraySidebar IDs that actually changed
Returns
TypeDescription
arraySelector 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
ParameterTypeDescription
$mappingsarraySidebar ID to template slug mapping (empty by default)
$changedElementsarraySidebar IDs that changed in this widget event (for example sidebar-1, footer-1)
$contextarrayTrigger context: trigger_class, trigger_event
Returns
TypeDescription
arraySidebar 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);

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
ParameterTypeDescription
$maparrayPost type → meta key patterns mapping (empty by default)
Returns
TypeDescription
arrayPost 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
ParameterTypeDescription
$maparrayHook name to reason key mapping (empty by default). Both values sanitized via sanitize_key().
Returns
TypeDescription
arrayHook-to-reason mapping

How this fits into the system

SystemPurpose
ekesto_ci_option_triggersDefine which options trigger invalidation
ekesto_ci_option_selectorsDefine what gets refreshed
ekesto_ci_global_triggersAlways 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
ParameterTypeDescription
$maparrayAssociative 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
ParameterTypeDescription
$selectorsarraySelector entries (internal defaults + extension-provided values).
$contextarrayOption trigger metadata (see table below).
$context keys
KeyDescription
trigger_classAlways option for this filter.
trigger_optionNormalized option key that triggered evaluation.
reasonNormalized reason key from ekesto_ci_option_triggers.
Returns
TypeDescription
arraySelector 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
ParameterTypeDescription
$modestringCurrent fanout mode (same_language or all_languages)
$contextarrayTrigger context metadata
$context reference
KeyDescription
trigger_classEvent family (for example post, timed, comment, presentation, async).
trigger_kindTrigger subtype used by fanout policy classification.
trigger_eventSpecific event key for the current trigger.
reasonInvalidation reason key. See Reason Keys Reference.
trigger_post_idTriggering post ID (0 for non-post triggers).
trigger_post_typeTriggering post type (empty for non-post triggers).
multilingual_fanout_post_triggers_enabledWhether post-like triggers default to all_languages.
multilingual_fanout_non_post_triggers_enabledWhether non-post triggers default to all_languages.
resolver_availableWhether a multilingual resolver is active.
trigger_language_availableWhether a trigger language is known for this event.
trigger_languageTrigger language code when available (optional).
intent, resolution, fallback, fallback_reasonOptional 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
TypeDescription
stringFanout 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
ParameterTypeDescription
$selectorsarraySelector entries (empty by default)
$contextarrayArchive resolution context (see table below)
$context reference
KeyDescription
sourceResolver source: post or term.
post_idChanged post ID (present when source=post).
post_typeChanged post type slug (present when source=post).
taxonomyChanged taxonomy slug (present when source=term).
term_idChanged term ID (present when source=term).
include_taxonomy_archivesWhether taxonomy archives are included
Returns
TypeDescription
arraySelector 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
ParameterTypeDescription
$urlsarrayResolved archive URLs (post type + taxonomy archives)
$contextarraySame context as ekesto_ci_archive_selectors
$context reference
KeyDescription
sourceResolver source: post or term.
post_idChanged post ID (present when source=post).
post_typeChanged post type slug (present when source=post).
taxonomyChanged taxonomy slug (present when source=term).
term_idChanged term ID (present when source=term).
include_taxonomy_archivesWhether taxonomy archive URLs are included in archive resolution for this run (boolean).
Returns
TypeDescription
arrayModified 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
ParameterTypeDescription
$postTypesarrayDefault public post types
Returns
TypeDescription
arrayModified 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

Hookreason in contextNotes
ekesto_ci_invalidation_urlsYesFull runtime context — includes all available reason values.
ekesto_ci_additional_urlsYesRuns before final output — urls and trusted_source are not available yet.
ekesto_ci_multilingual_fanout_modeYesRuntime trigger context used for fanout policy.
ekesto_ci_post_selectorsYesPost trigger context (subset of full reason space).
ekesto_ci_archive_selectorsNoDoes not use reason — relies on archive context (post_id, term_id, etc.).
ekesto_ci_archive_urlsNoSame context shape as ekesto_ci_archive_selectors.

Custom reason keys (extension maps)

You can define your own reason values using:

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
ParameterTypeDescription
$resolverLanguageResolverInterface|nullAuto-detected resolver instance, or null if none detected
Returns
TypeDescription
LanguageResolverInterface|nullResolver instance or null

Most installations should not use this filter. Custom resolvers should implement both LanguageResolverInterface and ResolverMetadataInterface.