WordPress Blocks Support
Wash: Blog Manager renders every native Gutenberg block to HTML for Shopify and saves the block structure as Shopify metafields. It also adds its own blocks for embedding Shopify products and collections in a post, and can optionally include the block styles so your theme can use them.
How block processing works
flowchart TD
A[WordPress Gutenberg Editor] --> B[Block-based Content]
B --> C[Wash Block Parser]
C --> D[WordPress Rendering]
D --> E[Clean HTML Output]
E --> F[Wash Processing]
F --> G[Shopify Article/Page]
B -.- B1["stored as comments"]
C -.- C1["Extract metadata, convert blocks, collect CSS"]
D -.- D1["apply_filters the_content"]
F -.- F1["URL rewriting, CDN replacement, link conversion"]
G -.- G1["HTML content, block metafields, optional CSS"]
Custom Wash blocks
Shopify product block
Embed a Shopify product card directly in your WordPress content:
- Add the "Shopify Product" block in the editor
- Search and select a product from your Shopify store
- Choose display style (Card, Minimal, or Full)
- Configure price and button visibility
In Shopify: The block renders using your theme's product-card snippet via Liquid:
{% assign product = all_products["your-product-handle"] %}
{% render "product-card", product: product %}
Shopify collection block
Display a grid of products from a Shopify collection:
- Add the "Shopify Collection" block in the editor
- Select a collection from your store
- Set the number of columns (2-6)
- Choose how many products to display (1-24)
In Shopify: The block loops through collection products:
{% for product in collections["your-collection"].products limit: 6 %}
{% render "product-card", product: product %}
{% endfor %}
Sync status block
An editor-only block showing the current sync status:
- Not synced: the post hasn't been synced yet
- Queued: a sync is in progress
- Synced: shows the Shopify URL and timestamp
- Error: shows the error message
This block is not rendered on the frontend.
Supported native blocks
Text blocks
| Block | WordPress | Shopify Output | Status |
|---|---|---|---|
| Paragraph | core/paragraph | <p> | Full support |
| Heading | core/heading | <h1> - <h6> | Full support |
| List | core/list | <ul>, <ol> | Full support |
| Quote | core/quote | <blockquote> | Full support |
| Code | core/code | <pre><code> | Full support |
| Preformatted | core/preformatted | <pre> | Full support |
| Pullquote | core/pullquote | <figure><blockquote> | Full support |
| Verse | core/verse | <pre> | Full support |
| Details | core/details | <details><summary> | Full support |
Media blocks
| Block | WordPress | Shopify Output | Status |
|---|---|---|---|
| Image | core/image | <figure><img> | Full support + CDN |
| Gallery | core/gallery | <figure> gallery | Full support + CDN |
| Audio | core/audio | <audio> | Partial (size limits) |
| Video | core/video | <video> | Limited (external recommended) |
| Cover | core/cover | <div> with background | Full support |
| Media & Text | core/media-text | Flex container | Full support |
| File | core/file | <a> download link | Full support |
Design blocks
| Block | WordPress | Shopify Output | Status |
|---|---|---|---|
| Columns | core/columns | Flex container | Full support |
| Group | core/group | <div> wrapper | Full support |
| Row | core/row | Flex row | Full support |
| Stack | core/stack | Flex stack | Full support |
| Separator | core/separator | <hr> | Full support |
| Spacer | core/spacer | <div> with height | Full support |
| Buttons | core/buttons | Button container | Full support |
| Button | core/button | <a> styled link | Full support |
Widget blocks
| Block | WordPress | Shopify Output | Status |
|---|---|---|---|
| Shortcode | core/shortcode | Rendered output | Depends on shortcode |
| Custom HTML | core/html | Raw HTML | Full support |
| Table | core/table | <table> | Full support |
Embed blocks
| Block | WordPress | Shopify Output | Status |
|---|---|---|---|
| YouTube | core/embed | <iframe> | Full support |
| Vimeo | core/embed | <iframe> | Full support |
| Twitter/X | core/embed | Embed script | Partial |
core/embed | Embed script | Partial | |
| Spotify | core/embed | <iframe> | Full support |
| SoundCloud | core/embed | <iframe> | Full support |
| TikTok | core/embed | Embed script | Partial |
Image handling
Automatic CDN migration
Images in blocks are automatically processed:
WordPress (before sync):
<!-- wp:image {"id":123} -->
<figure class="wp-block-image">
<img src="https://yourblog.com/wp-content/uploads/2024/01/photo.jpg" alt="Description"/>
</figure>
<!-- /wp:image -->
Shopify (after sync):
<figure class="wp-block-image">
<img src="https://cdn.shopify.com/s/files/1/0123/4567/files/photo.jpg" alt="Description"/>
</figure>
Srcset handling
WordPress responsive images (srcset) are simplified for Shopify's CDN:
<!-- WordPress srcset removed -->
<!-- Shopify CDN handles responsive images via image_url filter -->
Internal link handling
Links to other WordPress posts/pages are converted to Shopify URLs:
WordPress:
<a href="https://yourblog.com/my-article/">Read more</a>
Shopify:
<a href="/blogs/news/my-article">Read more</a>
Block styling
CSS class preservation
Block CSS classes are preserved in Shopify:
<p class="has-large-font-size has-primary-color">Styled text</p>
Inline styles
Inline styles from blocks are kept:
<div style="background-color:#f5f5f5;padding:20px">Content</div>
Theme compatibility
For best results, ensure your Shopify theme includes basic block styles:
/* Add to your Shopify theme CSS */
/* Columns */
.wp-block-columns {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
.wp-block-column {
flex: 1;
min-width: 200px;
}
/* Buttons */
.wp-block-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.wp-block-button__link {
display: inline-block;
padding: 0.75rem 1.5rem;
background: var(--color-primary, #000);
color: #fff;
text-decoration: none;
border-radius: 4px;
}
/* Quote */
.wp-block-quote {
border-left: 4px solid var(--color-primary, #000);
padding-left: 1rem;
margin-left: 0;
font-style: italic;
}
/* Separator */
.wp-block-separator {
border: none;
border-top: 2px solid #e0e0e0;
margin: 2rem 0;
}
/* Cover */
.wp-block-cover {
position: relative;
background-size: cover;
background-position: center;
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.wp-block-cover__inner-container {
position: relative;
z-index: 1;
}
/* Media & Text */
.wp-block-media-text {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
align-items: center;
}
@media (max-width: 768px) {
.wp-block-media-text {
grid-template-columns: 1fr;
}
}
/* Table */
.wp-block-table table {
width: 100%;
border-collapse: collapse;
}
.wp-block-table th,
.wp-block-table td {
border: 1px solid #e0e0e0;
padding: 0.75rem;
text-align: left;
}
/* Gallery */
.wp-block-gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.wp-block-gallery .wp-block-image {
margin: 0;
}
Blocks with limited support
Dynamic blocks
Some WordPress blocks render dynamic content that won't work in Shopify:
| Block | Issue | Workaround |
|---|---|---|
| Latest Posts | Queries WP database | Use Shopify blog sections |
| Categories | WP-specific | Manual list or Shopify tags |
| Archives | WP-specific | Remove or replace |
| Comments | WP comments | Use Shopify comments |
| Search | WP search | Shopify search |
| Navigation | WP menus | Shopify navigation |
| Site Title/Logo | WP settings | Shopify theme settings |
Script-dependent embeds
Some embeds require JavaScript that may not execute properly:
| Embed | Status | Solution |
|---|---|---|
| Twitter/X | May not render | Use static screenshot or link |
| May not render | Use static image or link | |
| TikTok | May not render | Use static image or link |
| May not render | Use static link |
Interactive blocks
Blocks requiring user interaction have limitations:
| Block | Status | Notes |
|---|---|---|
| Forms | Won't work | Use Shopify forms or third-party |
| Accordions (plugins) | May work | Depends on implementation |
| Tabs (plugins) | May work | Depends on implementation |
| Sliders (plugins) | Usually won't work | Use Shopify sections |
Third-party block plugins
Supported (HTML-based)
These plugin blocks generally work because they render to standard HTML:
- Kadence Blocks: most layout blocks work
- Stackable: basic blocks work
- GenerateBlocks: layout blocks work
- CoBlocks: most blocks work
Limited support
These may have issues:
- Elementor: limited support, because of its complex structure
- Divi Builder: not recommended; it uses a proprietary format
- WPBakery: partial support; it relies heavily on shortcodes
- Beaver Builder: limited support
Recommendation
For best compatibility:
- Use native WordPress blocks when possible
- Test third-party blocks before relying on them
- Avoid blocks that require plugin-specific JavaScript
Troubleshooting
Block not rendering
Symptoms: Block appears as comment or raw code
Solutions:
- Ensure block plugin is active in WordPress
- Check if block requires frontend JavaScript
- Test by viewing rendered HTML in WordPress before sync
Styling lost
Symptoms: Content appears but styling is missing
Solutions:
- Add WordPress block CSS to Shopify theme
- Check if inline styles are preserved
- Use Custom CSS in Shopify theme editor
Images missing
Symptoms: Images show broken or old URLs
Solutions:
- Re-sync the article
- Check image sync settings in Wash
- Verify images uploaded to Shopify Files
Embeds not working
Symptoms: Embed shows link instead of content
Solutions:
- Check if embed requires JavaScript
- Use iframe-based embeds when possible
- Consider static alternatives (screenshots, links)
Block metadata
Wash automatically extracts and syncs block metadata to Shopify metafields under the wash_blocks namespace:
| Metafield | Description |
|---|---|
wash_blocks.structure | JSON array of all blocks with their types and attributes |
wash_blocks.counts | Count of each block type used |
wash_blocks.total_count | Total number of blocks |
wash_blocks.shopify_refs | References to Shopify products and collections |
wash_blocks.headings | The post's headings, handy for a table of contents |
wash_blocks.image_ids | IDs of the images in the post |
wash_blocks.css | Block CSS, when Include Block CSS is on |
Metafield structure example
The wash_blocks.structure metafield contains a JSON array with block information:
{
"blocks": [
{
"name": "core/paragraph",
"attrs": { "align": "left" }
},
{
"name": "core/image",
"attrs": { "id": 123, "sizeSlug": "large" }
},
{
"name": "wash/shopify-product",
"attrs": { "productHandle": "premium-coffee", "style": "card" }
},
{
"name": "core/columns",
"attrs": {},
"innerBlocks": [
{ "name": "core/column", "attrs": { "width": "50%" } },
{ "name": "core/column", "attrs": { "width": "50%" } }
]
}
],
"version": 2
}
Using block metadata in Liquid
Access block data in your Shopify theme Liquid templates:
Basic access
{% comment %} Access block structure metafield {% endcomment %}
{% assign blocks_json = article.metafields.wash_blocks.structure.value %}
{% assign refs = article.metafields.wash_blocks.shopify_refs.value %}
{% comment %} Check if article contains Shopify product embeds {% endcomment %}
{% if refs.products.size > 0 %}
<div class="article-featured-products">
<h3>Featured Products in This Article</h3>
<div class="product-grid">
{% for handle in refs.products %}
{% assign product = all_products[handle] %}
{% if product %}
{% render 'product-card', product: product %}
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
Display block count statistics
{% comment %} Show content statistics {% endcomment %}
{% assign counts = article.metafields.wash_blocks.counts.value %}
{% if counts %}
<div class="article-stats">
{% if counts.image > 0 %}
<span class="stat">๐ท {{ counts.image }} images</span>
{% endif %}
{% if counts.video > 0 %}
<span class="stat">๐ฌ {{ counts.video }} videos</span>
{% endif %}
{% if counts.embed > 0 %}
<span class="stat">๐ {{ counts.embed }} embeds</span>
{% endif %}
</div>
{% endif %}
Render embedded collections
{% comment %} Render all collections referenced in the article {% endcomment %}
{% assign refs = article.metafields.wash_blocks.shopify_refs.value %}
{% if refs.collections.size > 0 %}
<section class="article-collections">
<h2>Shop the Collections</h2>
{% for collection_handle in refs.collections %}
{% assign collection = collections[collection_handle] %}
{% if collection %}
<div class="collection-preview">
<h3>{{ collection.title }}</h3>
<div class="product-row">
{% for product in collection.products limit: 4 %}
{% render 'product-card', product: product %}
{% endfor %}
</div>
<a href="{{ collection.url }}" class="btn">View All {{ collection.title }}</a>
</div>
{% endif %}
{% endfor %}
</section>
{% endif %}
Conditional styling based on blocks
{% comment %} Add CSS class based on block content {% endcomment %}
{% assign blocks_json = article.metafields.wash_blocks.structure.value %}
{% assign has_gallery = false %}
{% assign has_video = false %}
{% if blocks_json %}
{% for block in blocks_json.blocks %}
{% if block.name == 'core/gallery' %}
{% assign has_gallery = true %}
{% endif %}
{% if block.name == 'core/video' or block.name == 'core/embed' %}
{% assign has_video = true %}
{% endif %}
{% endfor %}
{% endif %}
<article class="article-content {% if has_gallery %}has-gallery{% endif %} {% if has_video %}has-media{% endif %}">
{{ article.content }}
</article>
Include block CSS
{% comment %} Include extracted block CSS if available {% endcomment %}
{% assign block_css = article.metafields.wash_blocks.css.value %}
{% if block_css and block_css != blank %}
<style>
{{ block_css }}
</style>
{% endif %}
Complete article template example
{% comment %} sections/article-template.liquid {% endcomment %}
<article class="article" itemscope itemtype="http://schema.org/BlogPosting">
<header class="article-header">
<h1 itemprop="headline">{{ article.title }}</h1>
{% comment %} Show content stats from blocks metadata {% endcomment %}
{% assign counts = article.metafields.wash_blocks.counts.value %}
{% if counts %}
<div class="article-meta">
<time datetime="{{ article.published_at | date: '%Y-%m-%d' }}">
{{ article.published_at | date: '%B %d, %Y' }}
</time>
{% if counts.image > 0 %}
<span class="meta-item">{{ counts.image }} images</span>
{% endif %}
</div>
{% endif %}
</header>
{% comment %} Include block CSS if enabled {% endcomment %}
{% assign block_css = article.metafields.wash_blocks.css.value %}
{% if block_css != blank %}
<style>{{ block_css }}</style>
{% endif %}
{% comment %} Main content {% endcomment %}
<div class="article-body" itemprop="articleBody">
{{ article.content }}
</div>
{% comment %} Featured products from article {% endcomment %}
{% assign refs = article.metafields.wash_blocks.shopify_refs.value %}
{% if refs.products.size > 0 %}
<aside class="article-products">
<h3>Products Mentioned</h3>
<div class="product-grid product-grid--{{ refs.products.size | at_most: 4 }}-col">
{% for handle in refs.products %}
{% assign product = all_products[handle] %}
{% if product %}
{% render 'product-card', product: product, show_quick_add: true %}
{% endif %}
{% endfor %}
</div>
</aside>
{% endif %}
</article>
Block settings
Configure block behavior under Settings > Wash Connector, on the Advanced tab, in the Gutenberg Blocks section:
| Setting | Default | Description |
|---|---|---|
| Enable Custom Blocks | On | Adds the Shopify Product, Shopify Collection and Sync Status blocks to the editor |
| Sync Block Structure | On | Saves the block structure to wash_blocks metafields |
| Include Block CSS | Off | Saves WordPress block styles to the wash_blocks.css metafield |
| RankMath FAQ Blocks | On | Rebuilds Rank Math FAQ blocks as <details> accordions with FAQPage data |
Shopify theme CSS
WordPress blocks use specific CSS classes that need styling in your Shopify theme. Add this CSS to properly render all block types.
Quick setup
- Create
assets/wp-blocks.cssin your Shopify theme - Include it in your article template:
{{ 'wp-blocks.css' | asset_url | stylesheet_tag }}
CSS variables
Customize these variables to match your theme:
:root {
--wp-primary-color: #000;
--wp-secondary-color: #666;
--wp-accent-color: #0073aa;
--wp-border-color: #e0e0e0;
--wp-background-light: #f8f9fa;
--wp-border-radius: 4px;
--wp-spacing: 1.5rem;
}
Essential block styles
Typography
.has-small-font-size { font-size: 0.875rem; }
.has-medium-font-size { font-size: 1.25rem; }
.has-large-font-size { font-size: 1.5rem; }
.has-x-large-font-size { font-size: 2rem; }
.has-text-align-left { text-align: left; }
.has-text-align-center { text-align: center; }
.has-text-align-right { text-align: right; }
Images & gallery
.wp-block-image img {
max-width: 100%;
height: auto;
border-radius: var(--wp-border-radius);
}
.wp-block-gallery {
display: grid;
gap: 1rem;
}
.wp-block-gallery.columns-2 { grid-template-columns: repeat(2, 1fr); }
.wp-block-gallery.columns-3 { grid-template-columns: repeat(3, 1fr); }
.wp-block-gallery.columns-4 { grid-template-columns: repeat(4, 1fr); }
.wp-block-gallery.is-cropped img {
width: 100%;
height: 200px;
object-fit: cover;
}
Cover block
.wp-block-cover {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
padding: 2rem;
overflow: hidden;
}
.wp-block-cover__image-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 0;
}
.wp-block-cover__background.has-background-dim {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1;
}
.wp-block-cover__inner-container {
position: relative;
z-index: 2;
color: #fff;
text-align: center;
}
Columns
.wp-block-columns {
display: flex;
flex-wrap: wrap;
gap: var(--wp-spacing);
}
.wp-block-column {
flex: 1;
min-width: 0;
}
@media (max-width: 768px) {
.wp-block-columns { flex-direction: column; }
.wp-block-column { flex-basis: 100%; }
}
Quote
.wp-block-quote {
padding: 1.5rem 1.5rem 1.5rem 2rem;
border-left: 4px solid var(--wp-accent-color);
background: var(--wp-background-light);
font-style: italic;
}
.wp-block-quote cite {
display: block;
margin-top: 1rem;
font-style: normal;
color: var(--wp-secondary-color);
}
.wp-block-quote cite::before {
content: "โ ";
}
Buttons
.wp-block-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.wp-block-button__link {
display: inline-block;
padding: 0.75rem 1.5rem;
background: var(--wp-primary-color);
color: #fff;
text-decoration: none;
border-radius: var(--wp-border-radius);
font-weight: 500;
}
.wp-block-button__link:hover {
opacity: 0.9;
}
Table
.wp-block-table table {
width: 100%;
border-collapse: collapse;
}
.wp-block-table th,
.wp-block-table td {
padding: 0.75rem 1rem;
border: 1px solid var(--wp-border-color);
text-align: left;
}
.wp-block-table thead th {
background: var(--wp-background-light);
font-weight: 600;
}
Separator
.wp-block-separator {
border: none;
border-top: 2px solid var(--wp-border-color);
margin: 2rem 0;
}
.wp-block-separator.is-style-dots {
border: none;
text-align: center;
}
.wp-block-separator.is-style-dots::before {
content: "ยทยทยท";
font-size: 1.5rem;
letter-spacing: 1rem;
color: var(--wp-secondary-color);
}
Embeds (YouTube, vimeo)
.wp-block-embed__wrapper {
position: relative;
width: 100%;
}
.wp-embed-aspect-16-9 .wp-block-embed__wrapper {
padding-bottom: 56.25%;
}
.wp-has-aspect-ratio .wp-block-embed__wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
Rank Math FAQ (third-party)
.rank-math-faq-item {
margin-bottom: 1rem;
border: 1px solid var(--wp-border-color);
border-radius: var(--wp-border-radius);
}
.rank-math-question {
margin: 0;
padding: 1rem;
background: var(--wp-background-light);
font-weight: 600;
}
.rank-math-answer {
padding: 1rem;
}
Download complete CSS
For a complete CSS file with all block styles, responsive breakpoints, and color utilities:
Best practices
- Sync a few posts and check how their blocks render before a bulk sync.
- Prefer native blocks; they survive the trip better than third-party ones.
- Add the WordPress block styles to your Shopify theme.
- Embed Shopify products with the Wash blocks instead of using static images.
- Replace dynamic blocks with Shopify-native features.
- Check that third-party embeds render.
- Expect complex layouts to need some adjustment on Shopify.
- Review synced posts on Shopify before you announce them.