Skip to main content

3 posts tagged with "WooCommerce"

View all tags

WooCommerce Blocks Showing core/missing After Upgrade? Block Theme Troubleshooting Guide

· 7 min read

Ran into these four WooCommerce-specific issues while building Block Themes for clients. Each relates to how FSE architecture interacts with WooCommerce's block system. Documenting the troubleshooting process to help others working on WooCommerce theme development.

TL;DR

Four common issues in WooCommerce Block Theme development: block rename causing core/missing (block names gained -block suffix after upgrade), product archive 404 after shop slug change (rewrite cache not flushed), template HTML mismatch with Gutenberg save (dynamic block validation failure), and Cart/Checkout templates not auto-assigning (manual assignment required). Each scenario includes copy-paste fixes.

Scenario 1: Blocks Show as core/missing After WooCommerce Upgrade

Problem

After upgrading WooCommerce, several blocks in the Site Editor display as core/missing (Block Recovery prompt). The frontend renders correctly, but these blocks can't be edited in the editor.

Root Cause

WooCommerce renamed multiple internal blocks in newer versions, adding a -block suffix:

Old nameNew name
cart-order-summary-subtotalcart-order-summary-subtotal-block
cart-order-summary-shippingcart-order-summary-shipping-block
cart-order-summary-taxescart-order-summary-taxes-block
cart-order-summary-totaltotals-block
proceed-to-checkoutproceed-to-checkout-block

If your templates or Patterns use old block comment names (e.g., <!-- wp:woocommerce/cart-order-summary-subtotal -->), Gutenberg can't find the corresponding block registration and renders it as core/missing.

Frontend rendering works because WooCommerce's PHP callbacks still support old names. But the editor depends on JavaScript block registration info—when it's not found, the block shows as missing.

Solution

Diagnose: Confirm registered block names in the browser console:

wp.blocks.getBlockTypes()
.map(b => b.name)
.filter(n => n.includes('cart-order-summary'))

Compare the output with the names used in your templates to find mismatches.

Fix: Batch replace old block names in template and Pattern files:

# Run in your theme directory
find . -name "*.html" -exec sed -i 's/woocommerce\/cart-order-summary-subtotal/woocommerce\/cart-order-summary-subtotal-block/g' {} +
find . -name "*.html" -exec sed -i 's/woocommerce\/cart-order-summary-shipping/woocommerce\/cart-order-summary-shipping-block/g' {} +
find . -name "*.html" -exec sed -i 's/woocommerce\/cart-order-summary-taxes/woocommerce\/cart-order-summary-taxes-block/g' {} +
find . -name "*.html" -exec sed -i 's/woocommerce\/cart-order-summary-total/woocommerce\/totals-block/g' {} +
find . -name "*.html" -exec sed -i 's/woocommerce\/proceed-to-checkout/woocommerce\/proceed-to-checkout-block/g' {} +

After replacing, clear the database template cache (see the previous article's "file changes not applying" scenario).

Important

  • Back up template files before upgrading WooCommerce—block renames typically appear in major version upgrades
  • Normal frontend rendering doesn't mean there's no issue—always check in the Site Editor
  • Subscribe to WooCommerce developer changelogs to learn about block name changes in advance

Scenario 2: Changed the Shop URL, Now the Product Page Is Gone

Problem

After changing the WooCommerce shop page slug (e.g., from /shop/ to /products/) or modifying the permalink structure, visiting the new shop URL returns 404 or displays as a plain page without the product listing.

Root Cause

WordPress's Rewrite API caches URL routing rules in the database on first access. After changing a page slug, the cached routing still maps to the old slug. The new URL has no matching route, so WordPress renders it as a regular page instead of a WooCommerce product archive.

Solution

After changing slugs or permalink structures, flush the rewrite cache:

# Local Docker environment
docker exec wp_cli wp rewrite flush --allow-root

# Server environment
ssh your-server "docker exec prod_cli wp rewrite flush --allow-root"

One command fixes it. You can also do this in the WordPress admin: Settings → Permalinks → Save Changes. Saving automatically flushes rewrite rules.

Best Practice

Run wp rewrite flush after any URL structure change—modifying slugs, changing permalinks, or adding custom post types. Consider adding it to your deployment script to avoid forgetting.

Scenario 3: Cart/Checkout Blocks Show Validation Errors

Problem

WooCommerce dynamic blocks like Cart and Checkout show Block validation failed in the Site Editor. The error message says Expected HTML and Actual HTML don't match.

Root Cause

Many WooCommerce blocks are server-side rendered (SSR) dynamic blocks—frontend content is generated by PHP in real time, not dependent on JavaScript's save() function. However, Gutenberg still requires the block markup in templates to exactly match the save() output.

If the HTML in your template doesn't match what Gutenberg expects (extra or missing classes, styles, or inner elements), validation errors are triggered.

Reference for WooCommerce block save output:

Blocksave output
product-price<div class="is-loading"></div>
price-filter<div class="wp-block-woocommerce-price-filter is-loading"><span aria-hidden="true" class="wc-block-product-categories__placeholder"></span></div>
woocommerce/cart<div class="wp-block-woocommerce-cart alignwide is-loading"></div>
woocommerce/checkout<div class="wp-block-woocommerce-checkout alignwide wc-block-checkout is-loading"></div>
cart-order-summary-*-block<div class="wp-block-woocommerce-{block-name}"></div>
proceed-to-checkout-block<div class="wp-block-woocommerce-proceed-to-checkout-block"></div>

Solution

Key rules:

  1. Blocks with html: false (save returns null, like core/query-pagination) can use self-closing tags
  2. Blocks where JS save returns placeholder HTML must not be self-closing—they need complete content
  3. core/query-pagination must not have a wrapper div; InnerBlocks go directly between open/close comments

Troubleshooting steps:

  1. Open the Site Editor and browser DevTools Console
  2. Find the Block validation failed log entry
  3. Compare Expected (Gutenberg-generated) vs. Actual (from your template)
  4. Fix the HTML using the reference table above

Safe to Ignore

These WooCommerce validation errors can be safely ignored:

ErrorExplanation
woocommerce/cart or woocommerce/checkout validation failureDynamic blocks rendered by PHP in real time; frontend displays correctly
woocommerce-blocktheme-css loading errorWooCommerce plugin CSS issue; wait for official fix

Scenario 4: Custom Cart/Checkout Template Not Showing Up

Problem

You created custom Cart or Checkout template files (e.g., templates/cart.html, templates/checkout.html), but the corresponding WooCommerce pages still use the default template. The custom template is visible in the Site Editor but hasn't been applied to the Cart/Checkout pages.

Root Cause

WooCommerce Cart and Checkout pages auto-match templates by name. But if matching fails (e.g., template slug doesn't exactly correspond, or page creation order issues), manual assignment is needed.

Pages that rely on shortcodes (like My Account) will never auto-match templates and always require manual assignment.

Solution

Manually assign templates to their corresponding pages:

# 1. Find the Cart page ID
docker exec wp_cli wp post list --post_type=page --fields=ID,post_title,post_name --allow-root | grep -i cart

# 2. Assign the template
docker exec wp_cli wp post meta update <page_id> _wp_page_template <template_slug> --allow-root

# Example: assign cart-block template to the Cart page
docker exec wp_cli wp post meta update 42 _wp_page_template cart-block --allow-root

Verify the assignment:

docker exec wp_cli wp post meta get <page_id> _wp_page_template --allow-root

Template Naming Tips

Ensure template file names correspond to WooCommerce page functions. Standard naming:

  • cart.html or cart-block.html → Cart page
  • checkout.html or checkout-block.html → Checkout page
  • single-product.html → Product detail page
  • archive-product.html → Product archive page (shop)

Consistent naming increases the auto-matching success rate.


Fixing 4 CSS Conflicts Between WooCommerce and FSE Block Themes

· 5 min read

While developing a WordPress FSE Block Theme with WooCommerce integration, I encountered 4 hidden traps where WooCommerce silently overrides theme styles. Each one was difficult to diagnose. Here are the root causes and solutions to help other FSE + WooCommerce theme developers.

TL;DR

  1. Font size preset override: WooCommerce registers small/medium/large/x-large presets that override theme.json. Use custom slugs like h-1~h-6 to avoid conflicts.
  2. Font-size class hyphenation: has-h-1-font-size(48px) vs has-h1-font-size(20px). Hand-written HTML must use the hyphenated version.
  3. Contrast color inversion: --wp--preset--color--contrast is set to a light color (#f8fafc) by WooCommerce, making white text invisible.
  4. ul.products pseudo-elements: WooCommerce injects ::before/::after that break CSS Grid layout. Fix with display:none.

Trap 1: Font Size Presets Overridden

Symptom

Defined font size presets in theme.json:

{
"settings": {
"typography": {
"fontSizes": [
{ "slug": "small", "size": "0.875rem" },
{ "slug": "large", "size": "1.125rem" }
]
}
}
}

After installing WooCommerce, paragraphs using has-large-font-size render at 36px instead of the expected 18px. H1 headings may also shrink from 48px to 20px.

Root Cause

WooCommerce registers its own font size presets via WP_Theme_JSON_API:

slugtheme.json originalWooCommerce override
small0.875rem (14px)13px
medium1rem (16px)20px
large1.125rem (18px)36px
x-large1.25rem (20px)42px

Presets with the same slug get overridden. Custom slugs (like h-1~h-6, base) are unaffected.

Solution

Use custom slugs in theme.json to avoid WooCommerce conflicts:

{
"fontSizes": [
{ "slug": "h-1", "size": "3rem" },
{ "slug": "h-2", "size": "2.25rem" },
{ "slug": "h-3", "size": "1.75rem" },
{ "slug": "base", "size": "16px" }
]
}

Use has-h-5-font-size instead of has-large-font-size in templates.

Trap 2: Font-Size Class Hyphenation

Symptom

When hand-writing FSE template HTML, H1 headings render at 20px instead of the theme.json value of 48px:

<!-- Wrong: renders at 20px -->
<h1 class="has-h1-font-size">Heading</h1>

<!-- Correct: renders at 48px -->
<h1 class="has-h-1-font-size">Heading</h1>

Two classes differ by one hyphen, but the rendered size differs by 2.4x.

Root Cause

Gutenberg renders fontSize: "h1" slug as has-h-1-font-size (hyphen added before the number). This is correct behavior.

But when WooCommerce overrides the font size system, has-h1-font-size (no hyphen) matches WooCommerce's override value (20px), while has-h-1-font-size (with hyphen) matches the theme.json original (48px).

Verify in browser console:

const el = document.createElement('div');
document.body.appendChild(el);

el.className = 'has-h1-font-size';
console.log('No hyphen:', getComputedStyle(el).fontSize); // 20px (wrong)

el.className = 'has-h-1-font-size';
console.log('With hyphen:', getComputedStyle(el).fontSize); // 48px (correct)

document.body.removeChild(el);

Solution

Always use the hyphenated format in hand-written FSE template HTML:

<!-- wp:heading {"fontSize":"h1"} -->
<h1 class="has-h-1-font-size">Heading</h1>
<!-- /wp:heading -->

<!-- wp:heading {"fontSize":"h3"} -->
<h2 class="has-h-3-font-size">Section Title</h2>
<!-- /wp:heading -->

Rule: has-h-{N}-font-size (with hyphen), applies to all h-* presets.

Trap 3: Contrast Color Inversion

Symptom

White text on a contrast background is invisible:

<div class="has-contrast-background-color">
<h2 class="has-base-color">White text on dark background</h2>
</div>

Expected contrast to be dark, but it renders as a very light background.

Root Cause

WooCommerce registers --wp--preset--color--contrast as #f8fafc (very light gray). This is WooCommerce's design intent (their default theme uses contrast as a light background section), but conflicts with most FSE themes where contrast means dark.

Solution

Use primary (dark) as text color on contrast backgrounds:

<div class="has-contrast-background-color">
<h2 class="has-primary-color has-text-color">Dark text on light background</h2>
</div>

Or define your own dark preset in theme.json (e.g. surface) instead of relying on contrast:

{
"settings": {
"color": {
"palette": [
{ "slug": "surface", "color": "#0f172a", "name": "Surface" }
]
}
}
}

Trap 4: ul.products Pseudo-Elements Break CSS Grid

Symptom

WooCommerce product listings using CSS Grid show misaligned cards with empty gaps:

ul.products {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}

Expected 3 equal columns, but empty slots appear and cards wrap to the next row.

Root Cause

WooCommerce injects ::before and ::after pseudo-elements on ul.products:

ul.products::before,
ul.products::after {
display: table;
content: '';
}

ul.products::after {
clear: both;
}

These pseudo-elements are designed as clearfix for traditional float layouts. But CSS Grid treats them as grid items, occupying two implicit grid cells.

Solution

Hide pseudo-elements for Grid layouts on ul.products:

ul.products {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}

ul.products::before,
ul.products::after {
display: none;
}

Important Notes

  • Each trap relates to WooCommerce's WP_Theme_JSON_API registration or CSS injection. Disabling WooCommerce removes these overrides.
  • Traps 1 and 2 are linked: after WooCommerce overrides font size presets, hyphenated and non-hyphenated classes map to different values.
  • Trap 3's contrast value may change across WooCommerce versions. Use a custom slug (e.g. surface) for stability.
  • Before hand-writing FSE templates, inspect Gutenberg's auto-rendered class names in DevTools. Don't guess class name formats.

Deploying a WooCommerce Site?

Recommended cloud hosting providers


Fix WooCommerce FSE Cart Block Blank Page and Product Image Collapse

· 4 min read

Encountered these two issues while developing a WooCommerce FSE Block Theme for a client: Cart Block renders a blank page when empty, and product cards collapse when no featured image is set. Here's the root cause and solution.

TL;DR

  1. Cart Block requires explicit filled-cart-block and empty-cart-block inner blocks -- without them, empty cart renders nothing.
  2. FSE's post-featured-image block renders an empty string when no thumbnail exists, causing card height collapse. Fix with post_thumbnail_html filter to inject WooCommerce placeholder.

Issue 1: Cart Block Blank Page on Empty Cart

Symptom

The cart page works fine with products, but after clearing the cart the entire content area becomes blank -- no message, no "Continue Shopping" button, users are stuck.

Root Cause

WooCommerce Cart Block (wp:woocommerce/cart) requires developers to explicitly declare two inner blocks:

  • wp:woocommerce/filled-cart-block -- shown when cart has items
  • wp:woocommerce/empty-cart-block -- shown when cart is empty

If you only add the Cart Block without these inner blocks, WooCommerce doesn't know what to render for an empty cart and outputs nothing.

This issue doesn't occur in classic themes because their PHP templates (cart.php) have built-in empty cart handling. But FSE HTML templates are declarative -- you must declare all states.

Solution

Correct structure in cart.html:

<!-- wp:woocommerce/cart {"className":"cclee-cart"} -->
<div class="wp-block-woocommerce-cart alignwide is-loading">

<!-- wp:woocommerce/filled-cart-block -->
<div class="wp-block-woocommerce-filled-cart-block">
<!-- Full layout for items: product list + totals -->
<!-- wp:columns -->
<div class="wp-block-columns">
<!-- wp:column {"width":"65%"} -->
<div class="wp-block-column" style="flex-basis:65%">
<!-- wp:woocommerce/cart-items-block -->
<div class="wp-block-woocommerce-cart-items-block"></div>
<!-- /wp:woocommerce/cart-items-block -->
</div>
<!-- /wp:column -->
<!-- wp:column {"width":"35%"} -->
<div class="wp-block-column" style="flex-basis:35%">
<!-- wp:woocommerce/cart-totals-block -->
<div class="wp-block-woocommerce-cart-totals-block">
<!-- wp:woocommerce/cart-order-summary-block /-->
<!-- wp:woocommerce/proceed-to-checkout-block /-->
</div>
<!-- /wp:woocommerce/cart-totals-block -->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
</div>
<!-- /wp:woocommerce/filled-cart-block -->

<!-- wp:woocommerce/empty-cart-block -->
<div class="wp-block-woocommerce-empty-cart-block">
<!-- Empty cart message + continue shopping button -->
<!-- wp:paragraph {"align":"center","textColor":"neutral-500"} -->
<p class="has-text-align-center has-neutral-500-color has-text-color">Your cart is currently empty.</p>
<!-- /wp:paragraph -->
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
<div class="wp-block-buttons">
<!-- wp:button {"backgroundColor":"accent","textColor":"base"} -->
<div class="wp-block-button"><a href="/shop/" class="wp-block-button__link has-base-color has-accent-background-color has-text-color has-background wp-element-button">Browse Products</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
</div>
<!-- /wp:woocommerce/empty-cart-block -->

</div>
<!-- /wp:woocommerce/cart -->

Key point: filled-cart-block and empty-cart-block must be direct children of wp:woocommerce/cart. WooCommerce uses them for conditional rendering based on cart state.

Symptom

In product listing pages (archive-product), products without featured images only show the text area -- no image placeholder. Mixed with products that have images, the layout heights are inconsistent and visually broken.

Root Cause

FSE's wp:post-featured-image block renders an empty string when a post/product has no thumbnail. Classic themes handle this in PHP templates with has_post_thumbnail() checks, but FSE HTML templates cannot embed conditional logic.

Solution

Add a filter in your theme's functions.php or WooCommerce integration file:

/**
* Product placeholder image when no featured image is set.
*
* FSE post-featured-image block renders empty when no thumbnail,
* causing card height collapse. This filter injects the WooCommerce
* placeholder image for product post type.
*
* @param string $html The post thumbnail HTML.
* @param int $post_id The post ID.
* @return string
*/
add_filter( 'post_thumbnail_html', function ( $html, $post_id ) {
// Already has image or not a product -- skip.
if ( $html || get_post_type( $post_id ) !== 'product' ) {
return $html;
}

// Use WooCommerce's built-in placeholder.
$src = function_exists( 'wc_placeholder_img_src' )
? wc_placeholder_img_src()
: '';

if ( ! $src ) {
return '';
}

return sprintf(
'<img src="%s" alt="%s" loading="lazy" decoding="async" style="width:100%%;height:100%%;object-fit:cover;">',
esc_url( $src ),
esc_attr( get_the_title( $post_id ) )
);
}, 10, 2 );

Notes:

  • wc_placeholder_img_src() depends on WooCommerce being active -- use function_exists() as a guard
  • object-fit: cover ensures the placeholder matches the cropping behavior of regular featured images
  • Only applies to product post type, does not affect blog posts or other post types

Interested in similar solutions? Get in touch