ppl / module-category-filter-cache-warmer
Standalone full-page-cache warmer for Magento 2: crawls category pages and configurable first product-list actions, including filters, sorting, page size, view mode and pagination, so shoppers reach warm built-in FPC or Varnish entries from the first clicks. Supports native Magento query-parameter navigation and PPL Discovery Filters without requiring another PPL module.
Ppl_CategoryFilterCacheWarmer
Standalone full-page-cache warmer for Magento 2 / Adobe Commerce. It crawls category pages and, optionally, the
first product-list action levels per store. Filters, sorting, direction, page size, view mode and pagination are
normal guest GET requests, so Magento's own Full Page Cache (built-in or Varnish) can be populated before
shoppers arrive — the first clicks are then cache HITs instead of cold renders.
It does not touch Magento's cache internals: no FPC identifier override, no VCL changes, no cache
plugins/observers, no purges. It only fetches normal storefront URLs; Magento/Varnish keep full control over
cache keys, X-Magento-Vary and tag-based invalidation. No dependency on other PPL modules.
Requirements
- Magento Open Source / Adobe Commerce 2.4.7–2.4.9
- PHP per your Magento version's system requirements (2.4.7 → 8.2/8.3, 2.4.8 → 8.3/8.4, 2.4.9 → 8.4/8.5).
Module code requires PHP>=8.2. - PHP
ext-curl,ext-dom,ext-libxml,ext-mbstring
Install
Composer:
composer require ppl/module-category-filter-cache-warmer
bin/magento module:enable Ppl_CategoryFilterCacheWarmer
bin/magento setup:upgrade
bin/magento setup:di:compile
Manual: copy this folder to app/code/Ppl/CategoryFilterCacheWarmer, then run the three bin/magento commands above.
Configure
Stores > Configuration > PPL > Category Filter Cache Warmer (all store-scoped, disabled by default):
- General > Enable Warmer — master switch (default off).
- Listing Action Depth is fixed at
1— category pages plus one product-list action (a filter, sorter,
direction, page-size, view-mode or pagination change), i.e. "level 0 + level 1". The admin field is hidden.
Depth 2+ multiplies the URL space far beyond a realistic full-page cache (a full 2-level warm of the sample
catalog produced ~2,700 URLs and overflowed a 100 MB Varnish roughly 3x, so the earliest-warmed pages were
LRU-evicted before shoppers arrived). A depth-1 warm of the same catalog is ~160 URLs and fits comfortably.
To restore configurable depth, revertConfig::getDepth()and un-hide the field insystem.xml. - Scope > Options Per Action Group / Max URLs Per Category — breadth caps. The crawler distributes the
available budget across distinct action signatures instead of allowing the first large filter to consume it.
Max URLs Per Category(default2000) counts logical listing states and bounds every category. With PPL
Discovery Filters, each logical state can produce both a full-page and an AJAX-fragment request, so the
physical request count can be almost twice the configured logical-state cap. - Performance > Parallel Requests / Timeout / Verify TLS — throttle + TLS (keep verify on in production).
- Schedule > Run On Schedule / Cron Expression — optional cron (global schedule; runs every enabled store).
Run
# all enabled stores (depth is fixed at 1: category pages + one product-list action)
bin/magento ppl:cfcw:warm
# one store, category pages only (--depth=0); exit non-zero if any URL errored (for CI/deploy)
bin/magento ppl:cfcw:warm --store=1 --depth=0 --fail-on-error
Output reports HIT / MISS / UNCACHEABLE / UNKNOWN / ERROR. A run holds a lock, so cron and CLI never overlap.
How it warms listing actions
The crawler parses Magento's rendered layered-navigation links and toolbar controls. It follows same-origin,
same-category links and turns rendered sorter, direction, page-size and view-mode controls into their canonical
Magento query parameters. A breadth-first traversal adds one real shopper action per level while preserving the
current listing state. Marketing/tracking noise is dropped; listing parameters such as
product_list_order/dir/limit/mode and p are retained.
When PPL Discovery Filters is active, the visible product update uses a cacheable ppl_df_fragment=1 request.
The warmer detects that mode and fetches both the full URL and its fragment variant. The fragment is a second
physical request for the same logical listing state, not another depth or budget slot.
Verifying (staging)
bin/magento cache:flush
bin/magento config:set ppl_category_filter_cache_warmer/general/enabled 1
bin/magento ppl:cfcw:warm --store=1 --depth=0
# then, as a guest:
curl -sI https://shop.example/some-category.html | grep -i x-magento-cache-debug # HIT
Known limitations
- Multi-store: guaranteed only for store views with a unique base URL or a store code in the URL. Several
store views on the same host without a store code may warm the default store. - Filters: native query-parameter layered navigation and PPL Discovery Filters are covered. Other
SEO/path-based or AJAX-only layered-navigation extensions may need an adapter. - Invalidation: the warmer never deletes cache. Run it after deploys /
cache:clean/ reindex — Magento
invalidates via tags as usual, and the warmer just re-fills afterwards. Do not auto-flush before warming. X-Magento-Cache-DebugHIT/MISS is only visible when the environment exposes it (Varnish, or built-in FPC in
developer mode); otherwise results are counted asUNKNOWN.
Changelog
All notable changes to Ppl_CategoryFilterCacheWarmer are documented here. This project follows
Semantic Versioning.
Unreleased
Fixed
- Discovery-Filters AJAX-fragment warming now works on the Hyvä build: detection reads the flat
configuration JSON indata-ppl-discoveryfilters-init(Hyvä) in addition to the per-component
data-mage-initJSON (Luma). Before, a Hyvä storefront was never detected, so NO
ppl_df_fragment=1twin URLs were warmed and every real filter click was an FPC MISS even after
a full warm (the warmer only requested full-page URLs no AJAX shopper ever fetches). - The PLAIN category URL +
ppl_df_fragment=1(the client's clear-all / history-navigation request)
is warmed too — the level-0 exclusion is gone. - Hyvä's NATIVE layered navigation (without Discovery Filters) is now crawlable: added the
filter-option/filter-options-contentclass tokens Hyvä uses (Luma's pluralfilter-options
token never matched them), so native filter links are discovered on Hyvä themes. - E2E-proven on a Hyvä + Discovery Filters store: full cold cache → one
ppl:cfcw:warmrun
(670 URLs = full pages + fragment twins) → the exact browser click URLs
(?ppl_df_fragment=1&ppl_filter[color][0]=…, clear-all fragment, full page) are all HIT on first
touch; a second warm pass reports 670 HIT / 0 MISS (no eviction). - Note: PPL Discovery Filters' own internal warmer (
ppl:discoveryfilters:cache:warm, cron warm,
caching_mode=warmed,warm_*config) was removed from Ppl_DiscoveryFilters/Ppl_DiscoveryFiltersHV —
this module is the single FPC warmer for native layered navigation AND Discovery Filters.
Changed
- Listing-action depth is now pinned to
1(category pages + one product-list action = "level 0 + level 1")
and the admin field is hidden. A full 2-level warm of the sample catalog produced ~2,700 URLs and overflowed
a 100 MB Varnish roughly 3x, so the earliest-warmed pages were LRU-evicted before shoppers arrived; a depth-1
warm of the same catalog is ~160 URLs and fits comfortably. The CLI--depthoverride still accepts0
(category pages only) but is capped at1. RevertConfig::getDepth()and un-hide thesystem.xmlfield to
restore configurable depth once warm scope is sized to cache capacity.
1.3.0 - 2026-07-10
First release.
Added
- Standalone full-page-cache warmer: crawls category pages and, optionally, the first product-list action levels
per store and issues guestGETrequests to pre-populate Magento's native FPC. - Breadth-first crawler that parses Magento's rendered layered-navigation links and toolbar controls. Each depth
level represents one filter, sorter, direction, page-size, view-mode or pagination action; listing parameters
are preserved while marketing/tracking noise is removed. - PPL Discovery Filters integration that warms the full listing URL and its cacheable
ppl_df_fragment=1
response without charging the fragment as another logical depth or budget slot. - Guest
X-Magento-Varybootstrap (per store, secure-aware base URL) so Varnish caches under the guest key. CategoryUrlProviderfilters to active, auto-generated, category-view rewrites (no 404 pre-warming).- Admin config (section
ppl_category_filter_cache_warmer, disabled by default), CLIppl:cfcw:warm
(--store,--depth,--fail-on-error), and an optional cron run. - Shared lock across CLI and cron so two warm runs can never overlap.
- Reporting counts
HIT / MISS / UNCACHEABLE / UNKNOWN / ERROR. - Same-origin enforcement (absolute, root-relative and protocol-relative links), HTTP/HTTPS-only requests,
off-host redirect protection, and TLS verification on by default (performance/verify_ssl). - Default per-category logical-state cap of
2000, with depth-2 and deeper budgets distributed by complete
action signatures so filter and toolbar groups receive a fair share of the top-N budget.
Notes
- Listing-action depth was originally configurable (0-3); as of Unreleased it is pinned to
1(see above). - Does not delete or invalidate cache itself — Magento/Varnish keep full control via cache tags.
- Multi-store: guaranteed only for store views with a unique base URL or a store code in the URL.
Requires 18
| Package | Constraint |
|---|---|
| ext-ctype | * |
| ext-curl | * |
| ext-dom | * |
| ext-libxml | * |
| ext-mbstring | * |
| magento/framework | >=103.0.7 <104.0.0 |
| magento/module-backend | >=102.0.0 <103.0.0 |
| magento/module-catalog | >=104.0.0 <105.0.0 |
| magento/module-catalog-url-rewrite | >=100.4.0 <101.0.0 |
| magento/module-config | >=101.2.0 <102.0.0 |
| magento/module-cron | >=100.4.0 <101.0.0 |
| magento/module-eav | >=102.1.0 <103.0.0 |
| magento/module-page-cache | >=100.4.0 <101.0.0 |
| magento/module-store | >=101.1.0 <102.0.0 |
| magento/module-url-rewrite | >=102.0.0 <103.0.0 |
| php | >=8.1 |
| psr/log | ^1.0 || ^2.0 || ^3.0 |
| symfony/console | ^6.4 || ^7.0 |
Compatibility
Each Magento release line is installed on its supported PHP versions, then the module is built (DI compilation + static-content deploy) and its unit and integration suites are run. The matrix shows the lines and PHP versions the module is confirmed to install and run on. Code-quality results further down (phpstan, phpcs, …) are reported separately and never affect compatibility.
Code Quality
Advisory checks against the module's source. Static analysis runs once across the whole module; PHPStan re-runs per Magento + PHP version because resolvable symbols differ between releases. These NEVER affect the Compatibility badge. A phpcs finding can't make a module incompatible.
Static analysis
Coding standards (phpcs), mess detection (phpmd), copy-pasted code (cpd), PHP cross-version compatibility, composer.json validity. Each runs once for the whole module.
| Tool | Status | Findings | Summary |
|---|---|---|---|
| PHPCS | Warning | 8 | 8 warnings (ruleset: Magento2), 6 auto-fixable with phpcbf |
| PHPMD | Warning | 18 | 18 rule violations (UnusedFormalParameter:12, ExcessiveClassComplexity:3, CountInLoopExpression:2, TooManyPublicMethods:1) |
| Cpd | Pass | 0 | |
| Composer validate | Info | 1 | valid; 1 advisory note (composer validate --strict) |
PHPStan
Type-checks the module's PHP against a real Magento install at the configured gate level. Re-runs per Magento and PHP version because resolvable symbols differ between releases.
Tests
Unit and integration suites, run for each applicable Magento and PHP version. A test failure speaks to the module's behaviour, not its compatibility with a Magento line, so it is reported here separately and never reddens the compatibility matrix.
Unit tests
Integration tests
| Magento | PHP 8.2 | PHP 8.3 | PHP 8.4 | PHP 8.5 |
|---|---|---|---|---|
| 2.4.7 | N/A | N/A | ||
| 2.4.8 | N/A | N/A | ||
| 2.4.9 | N/A | N/A |
Security
Security checks run directly against the module: an audit of its declared dependencies for known vulnerabilities (composer audit) and a scan of its source for malware and web-shell signatures. Each runs once. A malware detection fails the version outright.
More from Pawel Pliousnin Digital Solutions
View vendorShared Search Intelligence engine, indexing, Magento integration and response contracts for the Luma and Hyva storefront adapters.
Foundation and anchor infrastructure for modular PPL admin reports; individual PPL report modules provide the actual reports.
Admin action board for Magento 2 that turns shop and PPL report signals into severity-scored task cards.
Standalone PPL Tracking & Privacy HV storefront package for Magento 2, compatible with Hyvä-based storefronts, with consent-aware behavioural tracking and data-subject tooling.
Turn an existing module into recurring revenue.
If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.