Why Your WordPress Block Pattern Changes Aren’t Showing Up
You edit a PHP pattern file. You flush the cache. You reload the page in a private window to rule out browser cache. The old markup is still there. You edit the file again, add a comment as a sanity check, reload again. Still wrong. The page is serving something that should not exist anymore.
This is not a caching problem. It is a WordPress database override — and on a block theme (FSE) it is one of the easiest classes of bug to spend an afternoon chasing.
Reading this mid-debug? Jump straight to The fix for the one-liner that clears it, or The development mode shortcut for the config flag that prevents the problem locally.
What happened
The setup: a WooCommerce block theme (FSE, full-site editing) with a custom single product template. The template references a PHP block pattern for the related products section — <!-- wp:pattern {"slug":"elayne/woocommerce/woo-product-related"} /--> in single-product.html. When WordPress sees that tag in a template, it evaluates the PHP pattern file on each request and renders the result. Dynamic. File-driven. In theory, no caching needed.
Two bugs needed fixing in the pattern:
- The product images had a visible empty strip below them — a height mismatch caused by
"imageSizing":"thumbnail"loading a square image into a container the CSS wanted to render at 3/4 aspect ratio. - The hover “Add to cart” button was completely absent. The product-collection template was missing the
wp:woocommerce/product-buttonblock entirely.
Both were simple PHP pattern file edits. Change imageSizing: "thumbnail" to aspectRatio: "3/4". Add one block line. Done in two minutes.
Except nothing changed on the page.
The diagnostic loop
The standard checklist ran out fast:
wp cache flush— cleared the object cache. Reload, same result.- Private window — ruled out browser cache. Same result.
- Re-edit the file with an obvious change — confirmed the file write was landing. Same result.
Two wrong assumptions in a row is a signal to stop assuming and start measuring.
The measuring tool here was a one-liner in Playwright’s browser evaluator, inspecting the actual rendered DOM:
const imgDiv = document.querySelector('.elayne-related-section .wc-block-components-product-image');
return imgDiv.getAttribute('data-image-sizing');
The output: "thumbnail".
The PHP file no longer had imageSizing in it. The rendered HTML did. Those two facts cannot both be true if WordPress is reading the PHP file.
What was actually happening
WordPress block themes load templates from the filesystem by default. But when a user opens a template in the Site Editor and saves it — even without changing anything — WordPress writes the current template content into the wp_template custom post type in the database. From that point on, the database copy wins. The file on disk is ignored.
The pattern reference <!-- wp:pattern {"slug":"..."} /--> was dynamic. But it never got evaluated, because WordPress was reading the DB version of the template, which had the pattern content already expanded into static block markup at the time of the last Site Editor save.
That static markup included:
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail","isDescendentOfQueryLoop":true} -->
Exactly what was in the file at the time of the last Site Editor save. Every PHP edit since then had silently no-oped against a DB record nobody knew existed.
Confirming it took one WP-CLI command:
wp post list --post_type=wp_template \
--fields=ID,post_name,post_status \
--path=web/wp --url=https://example.com/store/
Output included 187 single-product publish. Checking post 187’s content showed the old block markup, verbatim.
The fix
Two options depending on how much of the DB override you want to keep. We documented both in detail in the wp-ops snippet library.
Option A — delete the override, restore file-system control
wp post delete 187 --force --path=web/wp --url=https://example.com/store/
wp cache flush --path=web/wp --url=https://example.com/store/
After deletion, WordPress falls back to the theme file. The dynamic <!-- wp:pattern --> reference is re-evaluated from PHP on every request. File changes take effect immediately.
Option B — surgically update the DB content
Use this when the DB override contains other customizations worth keeping. The technique is a wp eval call with a str_replace on the post content. Replace $old and $new with the exact <!-- wp:... --> markup — grab the current DB content with wp post get 187 --field=content first so you have the literal string to match:
wp eval '
$post_id = 187;
$content = get_post($post_id)->post_content;
$old = "... old block markup ...";
$new = "... new block markup ...";
if (strpos($content, $old) !== false) {
wp_update_post(["ID" => $post_id, "post_content" => str_replace($old, $new, $content)]);
echo "Updated\n";
}
' --path=web/wp --url=https://example.com/store/
The full snippet includes exact block markup strings, Trellis VM variants, and a multisite --url= reminder.
Why this is easy to miss
The Site Editor silently promotes templates from filesystem to database. There is no notice. There is no flag in the admin UI that says “this template is being served from the database, not the theme file.” You find out by accident — usually after ruling out browser cache, object cache, and WP_DEVELOPMENT_MODE one by one.
The DOM inspection step is what surfaces it. If the rendered HTML contains attribute values from a file you have already changed, the file is not what WordPress is reading.
Two other places the same problem hides:
- Pages rebuilt with a demo rebuild script. If a page’s
post_contentwas written from the old pattern and the page was never re-rebuilt, it carries the old static markup. - Synced patterns stored in
wp_blockthat reference other patterns. The inner reference gets frozen at creation time.
In all cases the diagnostic is the same: check the rendered DOM, compare against the current source file, identify which attributes are stale, find which DB record is still carrying them.
The development mode shortcut
For local development, WP_DEVELOPMENT_MODE=theme makes WordPress ignore DB-stored templates and always read from the filesystem. Add it to config/environments/development.php in Bedrock:
Config::define( 'WP_DEVELOPMENT_MODE', 'theme' );
With this enabled, PHP pattern file changes take effect on the next request — no cache flush, no WP-CLI. It should not be used in production (performance cost), but it eliminates this entire class of silent override problems for local development.
What shipped
After the fix, the related products section renders correctly: consistent 3/4 aspect ratio, no empty strip below the images, and a hover “Add to cart” button matching the homepage behavior. The PHP pattern file is the source of truth again, and the DB override was brought into sync with the surgical wp eval approach.
The final check was wp-pattern-sentinel, which launches a real browser, logs into WP admin, inserts the pattern into a draft page, and reads back any JS block validation errors. It passed clean — no content mismatches, no save-function drift. (For why that headless check matters as part of a broader validation pipeline, see the four-pass pattern validation system.)
Takeaways
Check for DB template overrides before debugging PHP file changes. One command:
wp post list --post_type=wp_template --fields=ID,post_name,post_status --path=web/wp --url=https://example.com/
If the template you are editing appears in that list, the DB copy is what WordPress is serving. The full detection and fix workflow — including Trellis VM variants — is in the wp-ops snippet.
DOM inspection is the right first diagnostic step, not cache flush. If the rendered data-* attributes or block comment attributes do not match the PHP file, the PHP file is not being read. Find out what is being read instead before adjusting any code.
WP_DEVELOPMENT_MODE=theme is not optional for FSE development. Without it, a single Site Editor save can silently freeze your template for months. Enable it in local environments.
The bugs themselves were two-minute fixes. Finding out why those fixes weren’t applying took longer than it should have. The answer was one WP-CLI command away the whole time.
About this work
This debugging session came out of development on Elayne, a WooCommerce block theme built for premium retail stores. The related products section, the hover add-to-cart button, the product-collection template — these are the kind of components that need to work correctly across single product pages, shop archives, and category templates simultaneously, with shared CSS and pattern-driven markup.
If you are building a WooCommerce store on a block theme and running into issues at this level — template inheritance, pattern rendering, the FSE editing pipeline — Imagewize works on exactly this stack. Agencies and developers looking for a WooCommerce block theme partner can reach out via the contact page.
Need Help with a Block Theme or FSE Build?
We build and debug WordPress block themes — full-site editing templates, pattern libraries, Site Editor workflows, and the WP-CLI plumbing that keeps them deployable. Fixed-price quotes and ongoing support available.
- FSE block theme builds and migrations from classic themes
- Block pattern libraries and Site Editor template work
- Pattern validation, debugging, and WP-CLI deployment workflows
- WooCommerce on block themes — single product, shop, and archive templates