A client emailed us eleven months after launch asking why the “Team” section on their new careers page looked nothing like the one on the About page. Same theme, same designer, same everything. The difference was that one had been built by an editor dragging columns around at 4pm on a Friday, and the other had been built by us.
That gap is the entire argument for WordPress block patterns. Not the inserter-full-of-pretty-layouts version you get from the marketing pages, but a curated, locked, version-controlled set of building blocks that makes the wrong layout hard to produce. We’ve shipped this on roughly forty client sites since the /patterns directory landed in WordPress 6.0, and the difference between a pattern library that survives two years and one that gets abandoned in month three comes down to about six decisions.
Here are those decisions, with the code.
- Theme-registered patterns in a
/patternsfolder are the default choice: they live in Git, deploy with the theme, and never need database migration. Synced patterns (the old reusable blocks) belong only to content that must be byte-identical everywhere, like a legal disclaimer or an opening-hours block. templateLock: "contentOnly"on the outer group of every pattern is the single highest-value line in the whole system. It turns a layout into a form with fields, and combined with filteringcanLockBlocksoff for non-admins, editors genuinely cannot unlock it.- Pattern overrides (WordPress 6.5 onwards, via
core/pattern-overridesblock bindings) give you a synced structure with editable text and images, which is what most people actually wanted from reusable blocks in the first place. - Kill the noise:
removethemesupport( 'core-block-patterns' )plusshouldloadremoteblockpatternsreturning false strips out several hundred irrelevant patterns and an HTTP request to WordPress.org on every editor load. - Images bundled with a theme pattern have no attachment ID, so they get no
srcset. Ship correctly sized WebP with explicit width and height, or design the pattern so the client replaces the image on first use.
Why most client pattern libraries fall apart
The common failure is not technical. It’s that the library was built as a demo rather than as a constraint. Someone exports thirty beautiful sections, drops them in the inserter, hands over a Loom video, and leaves. Six weeks later the client has fourteen variants of a hero section because nothing stopped them from deleting the image block and pasting in a full-width video.
The second failure is duplication with no source of truth. Half the patterns live in the database as user patterns created in the editor, half live in the theme, and nobody knows which is which. Staging gets refreshed from production, the theme deploy overwrites nothing, and the two sets drift apart. We’ve inherited sites where the same “CTA banner” existed four times: twice as unsynced user patterns, once as a synced pattern, once hardcoded in a template part.
Fix both problems with one rule. The theme owns structure. The database owns content. Everything else follows from that.

Synced, unsynced and overrides: decide per pattern
WordPress 6.3 folded reusable blocks into the pattern system and renamed them synced patterns. They’re still the same wp_block post type underneath, which matters more than the rename suggests, because it means they’re database rows. Database rows do not deploy.
Here’s the decision we use, and it takes about ten seconds per pattern:
- Unsynced theme pattern. Anything that is a layout starting point: hero, feature grid, pricing table, testimonial row, contact section. The client inserts it, fills it in, and their copy is theirs. This is 90 percent of a real library.
- Synced pattern. Content that must change in one place and update in forty: a compliance footnote, a seasonal promo bar, a phone number that appears in twelve sections. Keep the count low. We aim for under six per site.
- Synced pattern with overrides. Repeating card structures where the frame is fixed but the text and image differ per instance. Team cards, service cards, case study tiles.
The trap with synced patterns is that editing one silently rewrites published pages with no obvious warning and no per-page revision trail. If a marketing team of eight has edit access, that’s a genuine risk. Reusable blocks earned their bad reputation honestly.
Building block patterns that live in the theme
A pattern is a PHP file in /patterns with a header comment. WordPress registers it automatically, translates the Title and Description against your text domain, and you get version control for free.
<?php
/**
- Title: Hero, split with image right
- Slug: acme/hero-split-image-right
- Categories: acme-hero
- Description: Full-width hero with headline, supporting copy, one button and a right-hand image.
- Keywords: hero, banner, intro
- Viewport Width: 1400
- Post Types: page
- Inserter: yes
*/
?>
<!-- wp:group {"templateLock":"contentOnly","align":"full","style":{"spacing":{"padding":{"top":"var:preset|spacing|80","bottom":"var:preset|spacing|80"}}},"layout":{"type":"constrained"}} -->
<div class="wp-block-group alignfull">
<!-- wp:columns {"verticalAlignment":"center"} -->
<div class="wp-block-columns are-vertically-aligned-center">
<!-- wp:column {"verticalAlignment":"center","width":"55%"} -->
<div class="wp-block-column is-vertically-aligned-center" style="flex-basis:55%">
<!-- wp:heading {"level":1,"fontSize":"xx-large"} -->
<h1 class="wp-block-heading has-xx-large-font-size"><?php eschtmle( 'A headline that fits on two lines', 'acme' ); ?></h1>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p><?php eschtmle( 'One or two sentences of supporting copy. Keep the placeholder realistic in length so the client can see when their copy is too long.', 'acme' ); ?></p>
<!-- /wp:paragraph -->
<!-- wp:buttons -->
<div class="wp-block-buttons"><!-- wp:button --><div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="#"><?php eschtmle( 'Get started', 'acme' ); ?></a></div><!-- /wp:button --></div>
<!-- /wp:buttons -->
</div>
<!-- /wp:column -->
<!-- wp:column {"verticalAlignment":"center","width":"45%"} -->
<div class="wp-block-column is-vertically-aligned-center" style="flex-basis:45%">
<!-- wp:image {"sizeSlug":"large","style":{"border":{"radius":"12px"}}} -->
<figure class="wp-block-image size-large has-custom-border"><img src="<?php echo escurl( getthemefileuri( 'assets/patterns/hero-split.webp' ) ); ?>" alt="" width="960" height="720" style="border-radius:12px" /></figure>
<!-- /wp:image -->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
</div>
<!-- /wp:group -->
Three things in there are not obvious. Viewport Width: 1400 controls the scale of the inserter preview, and if you leave it off, full-width sections render as an unreadable smudge. Post Types: page keeps the pattern out of the blog post inserter, which is how you stop hero sections appearing inside articles. And getthemefileuri() rather than gettemplatedirectoryuri() means a child theme can override the image by dropping a file at the same path.
The image is also the known weak point. Because there’s no attachment ID, WordPress cannot generate a srcset, so a 1920px hero gets served to a phone. Ship the asset as WebP at the size the layout actually needs, set explicit width and height to protect CLS, and accept that the client replacing it from the Media Library is the upgrade path.
Getting the markup out of the editor
Don’t hand-write block markup. Build the section in the editor at full width, select the outer block, use Copy from the block toolbar, paste into your PHP file, then do two find-and-replaces: swap uploaded image URLs for getthemefile_uri() calls, and strip every "id":123 attribute left behind by the media library. Missed IDs are the cause of the classic “broken image on the staging site” ticket.
Categories, naming and treating the inserter as UI
Default WordPress ships with several hundred patterns across core categories, plus a live fetch from the pattern directory. For a client build, that’s noise you’re asking a non-technical editor to filter through. Turn it off.
addaction( 'aftersetup_theme', function () {
// Removes all core-bundled patterns (Text, Gallery, Call to Action, etc.)
removethemesupport( 'core-block-patterns' );
} );
// Stops the editor calling out to the WordPress.org pattern directory on load.
addfilter( 'shouldloadremoteblock_patterns', '__return_false' );
add_action( 'init', function () {
$categories = array(
'acme-hero' => __( 'Hero sections', 'acme' ),
'acme-content' => __( 'Content sections', 'acme' ),
'acme-proof' => __( 'Testimonials & logos', 'acme' ),
'acme-cta' => __( 'Calls to action', 'acme' ),
);
foreach ( $categories as $slug => $label ) {
registerblockpattern_category( $slug, array( 'label' => $label ) );
}
} );
Four to six categories is the sweet spot. Go past eight and the client scrolls instead of scanning. Name patterns the way the client describes them, not the way you built them: “Three services with icons” beats “Grid 3col icon variant B” every single time. It also makes the search box in the inserter work, because they’ll type “services”.
Prefix every slug with your theme namespace (acme/). It costs nothing and it’s what lets you call unregisterblockpattern() confidently later.
Locking is what separates a library from a liability
Content-only locking is the feature that makes this whole approach work for clients. Set "templateLock":"contentOnly" on the outermost group and the editor stops showing block settings for the children. Instead the client gets a plain list of editable fields in the sidebar: text, images, links. No spacing controls, no colour pickers, no ability to delete the second column.
For finer control, lock individual blocks:
<!-- wp:heading {"lock":{"move":true,"remove":true}} -->
<h2 class="wp-block-heading">This heading cannot be moved or deleted</h2>
<!-- /wp:heading -->
Here’s the part the tutorials skip. Content-only locking shows a Modify button, and any user who can see it can unlock the group. If you want the lock to hold, remove the locking UI for everyone below administrator:
addfilter( 'blockeditorsettingsall', function ( $settings ) {
if ( ! currentusercan( 'manage_options' ) ) {
$settings['canLockBlocks'] = false; // hides both the lock toolbar item and the Modify button
}
return $settings;
}, 10, 2 );
Pair that with hard limits in theme.json: settings.color.custom: false, settings.color.customGradient: false, settings.typography.customFontSize: false, and a short spacing.spacingSizes scale. A locked pattern with an unlocked colour picker is still a brand problem waiting to happen.
The caveat: if the client’s team includes a competent in-house designer who genuinely needs to build new layouts, aggressive locking makes you the bottleneck. Give that person an Administrator account and lock the rest. Governance is a people decision that you implement in code, not the other way round.
Pattern overrides for repeat content
Since WordPress 6.5, a synced pattern can expose specific blocks as editable per instance using block bindings. This is the answer for card grids, staff profiles and case study tiles where the frame must never drift but the content always does.
Inside the synced pattern, give each editable block a name in metadata and bind it to core/pattern-overrides:
<!-- wp:group {"metadata":{"name":"Team card"},"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:image {"metadata":{"name":"Photo","bindings":{"url":{"source":"core/pattern-overrides"},"alt":{"source":"core/pattern-overrides"}}}} -->
<figure class="wp-block-image"><img src="" alt="" /></figure>
<!-- /wp:image -->
<!-- wp:heading {"level":3,"metadata":{"name":"Name","bindings":{"content":{"source":"core/pattern-overrides"}}}} -->
<h3 class="wp-block-heading">Full name</h3>
<!-- /wp:heading -->
<!-- wp:paragraph {"metadata":{"name":"Role","bindings":{"content":{"source":"core/pattern-overrides"}}}} -->
<p>Job title</p>
<!-- /wp:paragraph -->
</div>
<!-- /wp:group -->
Overridden values are stored on the instance, so the page markup ends up looking like <!-- wp:block {"ref":412,"content":{"Name":{"content":"Priya Raman"}}} /-->. Change the card’s padding in the source pattern and all forty instances update. Change a name and only that one does.
Two limits worth knowing before you design around this: overrides work with paragraph, heading, image and button blocks only, and the pattern has to be synced, which puts it back in the database. If you need genuinely structured repeating data (filters, sorting, an archive), you want a custom post type and a Query Loop, not a pattern. We’ve watched teams build 60-card “team libraries” out of overrides and then discover they can’t sort by department.
Keeping it fast, and keeping it alive
Every registered pattern is parsed and its preview rendered when the inserter opens. A library of 25 well-chosen patterns opens instantly. Push past 150 and you’ll feel the inserter hesitate on modest hardware, particularly with heavy nested column patterns. Curate hard. If a pattern hasn’t been inserted in six months, delete it.
The maintenance habits that have actually held up for us:
- One file, one pattern, named after the slug.
patterns/hero-split-image-right.php. Trivially greppable. - A hidden style guide page built from every pattern in the library, set to
noindex. It’s your visual regression check after a WordPress or theme update, and it’s the handover document. - Realistic placeholder copy. Lorem ipsum hides layout failures. Write placeholders at the length the real copy will be.
- Test the responsive breakpoints inside the pattern preview not just on the front end. A pattern that only looks right at 1400px viewport width teaches the client nothing.
If you’re building from scratch every time, you’re doing unpaid R&D. The reason we built CanvasWP around a large, pre-locked pattern set is that the same fifteen section types cover the majority of marketing sites, and the value you add for a client is in the constraints and the copy, not in rebuilding a testimonial slider for the ninetieth time.
Frequently Asked Questions
Are reusable blocks deprecated in WordPress?
They’re renamed, not removed. Since WordPress 6.3 they’re called synced patterns, and they still use the same wp_block post type, so existing reusable blocks continue to work with no migration needed. What changed is the UI: synced and unsynced patterns now live together in the same Patterns screen under Appearance.
Should client patterns go in the theme or be created in the editor?
Put layout patterns in the theme’s /patterns directory. They then live in Git, deploy with the rest of the code, and survive a database refresh from production to staging. Reserve editor-created patterns for content the client owns and edits themselves, and accept those will need a database export to move between environments.
How do I stop clients breaking a pattern after inserting it?
Add "templateLock":"contentOnly" to the outer group block in the pattern markup, which reduces the editing experience to filling in text and images. Then filter blockeditorsettingsall to set canLockBlocks to false for users without manageoptions, otherwise they can click Modify and unlock it. Back both up with theme.json settings that disable custom colours and font sizes.
Do block patterns work in classic themes?
Yes. The /patterns directory auto-registration and registerblockpattern() both work in classic themes as long as the block editor is in use for the post type. What you lose is theme.json-driven design controls and template part patterns, so patterns in a classic theme need their styling handled by the theme’s own stylesheet.
Why do images in my theme patterns look blurry or oversized on mobile?
Because a theme-bundled image has no attachment ID, WordPress cannot generate the srcset that normally serves smaller versions to smaller screens. Export the asset as WebP at the maximum size the layout needs, add explicit width and height attributes, and encourage the client to replace it from the Media Library, which restores responsive sizing on that instance.
If you take one thing into your next build, make it this: write the pattern, then lock it, then delete two patterns you don’t need. A library of twenty patterns that the client can’t break is worth more than a hundred they can. Start with the five sections that appear on every page of the site you’re building right now, get those into /patterns with content-only locking, and grow the set only when a real page needs something the library can’t produce.

Leave a Reply