Auto-Purge Cache for CPT Archive & Sidebar Pages on Publish
When a new post is published (or an existing one edited) in any of the custom post types — communications, podcast, articles, testimonials, events — the listing/archive pages and any sidebar that displays that content should purge automatically at every cache layer. No more manual “purge this page” after each publish.
Key finding: this is not a new plugin
Section titled “Key finding: this is not a new plugin”The capability already exists and is half-wired. It lives in one existing mu-plugin and only needs completing.
- The auto-purge engine is
cloudflare-auto-purge.php(v1.4.0), already on the server atsites/baseworks.com/mu-plugins/cloudflare-auto-purge.php. - It already hooks
save_post(line 315) for all post types, skips autosaves/revisions, purges the post’s own URL, and — viabw_get_related_pages_for_post_type()(lines 93–119) — purges “related listing pages” for that post type. - That map currently covers only two post types:
eventandpost. The four CPTs the user cares about (communications, podcast, article, testimonial) are simply not in the map. That is the entire reason their archives go stale.
Therefore:
- Do not create a separate plugin. It would duplicate the Cloudflare auth helpers and URL-variation logic already in this file for zero benefit.
- Do not touch
bw-four-layer-cache-purge.php— that is only the admin-bar “4-Layer Cache Purge” button, not the auto-purge engine. The two files are easy to conflate but are separate concerns. - Do extend
bw_get_related_pages_for_post_type()and add matching nginx-layer coverage incloudflare-auto-purge.php.
The two-layer trap (why “just add rows to the map” is not enough)
Section titled “The two-layer trap (why “just add rows to the map” is not enough)”The related-pages map is consumed only inside
bw_purge_cloudflare_on_post_save() — i.e. it only purges Cloudflare.
- nginx-helper’s own
purge_archive_on_editpurges the CPT archive URL (/article/) but never the friendly Elementor listing page (/blog/,/communication/, etc.). - Events dodge this today with a site-wide
nginx purge_allonsave_post_event(lines 335–345), justified because events publish infrequently and nginx rebuilds fast.
So if we only add four rows to the Cloudflare map, Cloudflare goes fresh but nginx keeps serving the stale archive. Completing the feature means each listing/sidebar URL must be purged at both layers.
Good news: the bw_fix_nginx_helper_cache_key filter (added 2026-06-25, lines
366–401) makes per-URL nginx purge reliable regardless of xCloud’s
trailing-slash cache-key state, by deleting both key variants. So we now have a
surgical option instead of the blunt purge_all.
Open questions to resolve BEFORE writing code
Section titled “Open questions to resolve BEFORE writing code”These must be answered first — the plan is gated on them.
1. Confirm the real friendly listing URLs (do NOT guess from CPT slugs)
Section titled “1. Confirm the real friendly listing URLs (do NOT guess from CPT slugs)”The human-facing pages do not match the register_post_type rewrite slugs.
Proven from docs/ELEMENTOR-CSS-CACHING-ISSUE.md and the CPT registrations:
| Post type | CPT rewrite slug (archive) | Real friendly listing page | Source |
|---|---|---|---|
post | (blog) | /blog/ + /ja/blog/ | already in map ✅ |
event | event | /events/, /events-archive/ + /ja/ | already in map ✅ |
communications | communications (plural) | /communication/ (singular) | Elementor doc ✅ |
podcast | podcast | /podcast/ | Elementor doc ✅ |
testimonial | testimonial (singular) | /testimonials/ (plural) | Elementor doc ✅ |
article | article | UNKNOWN — confirm on live site | ⚠️ |
instructor | instructor | /instructors/ | in scope? (see Q3) |
Note the singular/plural flips for communications and testimonials — a map built from CPT slugs would purge the wrong URL. Before build:
- Confirm
/communication/,/podcast/,/testimonials/are the live listing pages (and whether each also has a CPT-archive page worth purging). - Find the real listing page for articles (
/article/?/articles/? folded into/blog/?). - Confirm the
/ja/counterpart exists for each.
2. What is “the sidebar,” and where does it render?
Section titled “2. What is “the sidebar,” and where does it render?”The user mentioned “sidebar purges.” This is the biggest scope-determining question:
- If the sidebar is part of each listing/archive page, purging those page URLs already covers it — no extra work.
- If the sidebar is a site-wide widget (e.g. “latest posts / upcoming events” shown on many pages, or on every single-post view), then its purge surface is every page that renders it, which no listing-URL map covers. That would require either enumerating those pages or accepting a broader purge.
Action: identify which pages carry the sidebar and whether it pulls cross-type “latest” content. This answer decides whether this is a ~5-URL map extension or something wider.
3. Is instructor in scope?
Section titled “3. Is instructor in scope?”The user did not name instructors (they rarely change). Recommend: leave out for now, add later if desired. Confirm.
Proposed implementation (once questions answered)
Section titled “Proposed implementation (once questions answered)”All changes in sites/baseworks.com/mu-plugins/cloudflare-auto-purge.php.
Step 1 — Extend the Cloudflare related-pages map
Section titled “Step 1 — Extend the Cloudflare related-pages map”Add confirmed rows to bw_get_related_pages_for_post_type():
$post_type_pages = array( 'event' => array( $site_url . '/events/', $site_url . '/ja/events/', $site_url . '/events-archive/', ), 'post' => array( $site_url . '/blog/', $site_url . '/ja/blog/', ), // NEW — URLs below pending confirmation (Q1): 'communications' => array( $site_url . '/communication/', $site_url . '/ja/communication/', ), 'podcast' => array( $site_url . '/podcast/', $site_url . '/ja/podcast/', ), 'testimonial' => array( $site_url . '/testimonials/', $site_url . '/ja/testimonials/', ), 'article' => array( // $site_url . '/<confirmed-article-listing>/', ),);Homepage (/ and /ja/) is already always purged — good, since it often shows
cross-type latest content.
Step 2 — Give the nginx layer the same coverage (the actual fix)
Section titled “Step 2 — Give the nginx layer the same coverage (the actual fix)”Pick ONE approach and apply it to the new CPTs:
- Option A — surgical (recommended). After the Cloudflare purge in
bw_purge_cloudflare_on_post_save(), also run nginx-helper’s per-URL purge on each related listing URL. Thebw_fix_nginx_helper_cache_keyfilter now makes this reliable. More targeted; no site-wide flush. - Option B — proven/blunt. Mirror the event pattern: register
save_post_communications,save_post_podcast, etc. handlers that callnginx_helper()->purge_all(). Simple and battle-tested, but flushes the whole nginx cache on every publish of these types.
Recommendation: Option A, because the both-variant filter that made it
reliable did not exist when the event purge_all workaround was written — the
surgical path is now the better default. Keep Option B as the fallback if
per-URL nginx purge proves flaky in testing for these specific URLs.
Step 3 — Sidebar (depends on Q2)
Section titled “Step 3 — Sidebar (depends on Q2)”- If sidebar = part of listing pages → already covered by Steps 1–2.
- If sidebar = site-wide widget → add the sidebar-bearing page URLs to the purge
set (or, if unbounded, fall back to the event-style
purge_allfor these publishes).
Testing (one type end-to-end before wiring the rest)
Section titled “Testing (one type end-to-end before wiring the rest)”Per the repo’s server-touching guardrails, prove it on one post type first:
- Pick
communications. Publish/update a test communication. - Confirm in
error_logthe[cloudflare-purge]line lists/communication/(+/ja/) and the homepage. - Hard-load
/communication/in an incognito window (bypassing browser cache) and confirm the new post appears without a manual purge. - Verify both Cloudflare (
cf-cache-statusheader flips to MISS then HIT) and nginx (new content served) went fresh. - Only after this passes, add podcast → article → testimonial the same way, testing each.
Guardrails / notes
Section titled “Guardrails / notes”- This is a mu-plugin edit on production
baseworks.com. Follow the state-what/why/done sequence and confirm before applying on the server. - Keep the
save_posthook (fires on edits too — desirable; autosaves and revisions are already skipped). No need to switch totransition_post_status. - Do NOT trigger Elementor CSS flushes — targeted URL purges do not delete Elementor CSS files, so this stays clear of the Feb 4 broken-CSS incident.
- Mirror the edited file back into the repo (
sites/baseworks.com/mu-plugins/) and updatedocs/CODE-INVENTORY.mdafter deploy. - Bump the plugin version header and add a dated
Updated:note.
Effort estimate
Section titled “Effort estimate”Small. Once Q1–Q3 are answered: ~1 focused session — a few rows in one map, one nginx-purge helper loop, and per-type testing. No new plugin, no new infrastructure.