Skip to content
ExponaBLOG
WORDPRESS DEVELOPMENT & PLUGINS · DAY 024 / 100

Block patterns: what every WordPress developer should know

Reusable layouts editors love.

Expona Admin8 min read

Introduction

Block patterns in WordPress represent one of the most underrated tools in a developer's toolkit for scaling design systems across client sites. When you understand how to register patterns in themes or plugins, leverage synced patterns for shared content, and organize them with proper categories, you unlock a workflow that editors absolutely love—and one that reduces support friction dramatically. Block patterns: what every WordPress developer should know starts with recognizing that they sit at the intersection of design consistency and editorial flexibility, giving non-technical users powerful layout building capabilities without exposing them to code.

In this guide, we'll walk through the mechanics of pattern registration, explore synced patterns for maintaining shared content, show you how to structure pattern discovery through categories, and demonstrate real-world landing page section examples that your team can ship today.

Registering patterns in themes or plugins

WordPress provides two primary ways to register block patterns: declaratively through block-patterns directory or programmatically via the register_block_pattern() function. Most teams we work with start with the declarative approach for theme patterns because it's simpler, but the programmatic route gives you more control when you need it.

The simplest path is creating a block-patterns folder in your theme root, then adding PHP files for each pattern. WordPress automatically loads these files during theme initialization.

php
// wp-content/themes/your-theme/block-patterns/hero-section.php
<?php
/**
 * Hero Section Pattern
 */
return array(
	'title'       => 'Hero Section',
	'description' => 'Full-width hero with image and CTA',
	'content'     => '<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
	<!-- wp:image {"sizeSlug":"full"} -->
	<figure class="wp-block-image size-full">
		<img src="' . esc_url( get_template_directory_uri() ) . '/images/hero.jpg" alt="" />
	</figure>
	<!-- /wp:image -->
	<!-- wp:heading -->
	<h2 class="wp-block-heading">Welcome to Our Site</h2>
	<!-- /wp:heading -->
	<!-- wp:buttons -->
	<div class="wp-block-buttons">
		<!-- wp:button -->
		<div class="wp-block-button">
			<a class="wp-block-button__link wp-element-button">Get Started</a>
		</div>
		<!-- /wp:button -->
	</div>
	<!-- /wp:buttons -->
</div>
<!-- /wp:group -->',
	'categories'  => array( 'hero', 'featured' ),
	'keywords'    => array( 'hero', 'cta', 'image' ),
	'viewportWidth' => 1280,
);

For plugin-based patterns or when you need conditional logic, use the programmatic approach. We typically hook into init with a priority of 12 or later (after post types are registered) to ensure all dependencies are available:

php
// In your plugin or theme functions.php
add_action( 'init', 'register_custom_patterns', 12 );

function register_custom_patterns() {
	register_block_pattern(
		'my-plugin/testimonial-grid',
		array(
			'title'       => 'Testimonial Grid',
			'description' => 'Three-column grid of testimonials',
			'content'     => '<!-- wp:group {"layout":{"type":"grid","columnCount":3}} -->
<div class="wp-block-group">
	<!-- wp:group -->
	<div class="wp-block-group">
		<!-- wp:paragraph -->
		<p>"Outstanding service from start to finish."</p>
		<!-- /wp:paragraph -->
		<!-- wp:paragraph -->
		<p><strong>— Jane Smith</strong></p>
		<!-- /wp:paragraph -->
	</div>
	<!-- /wp:group -->
</div>
<!-- /wp:group -->',
			'categories'  => array( 'testimonials' ),
			'keywords'    => array( 'testimonial', 'social proof', 'review' ),
		)
	);
}

The key attributes to nail down: title shows in the inserter, content is the actual block markup (get this from the code editor view), categories organizes patterns, and viewportWidth controls how the preview renders in the pattern preview.

Synced patterns for shared content

Synced patterns (formerly "reusable blocks") are game-changers when you need a layout or content snippet that updates everywhere it's used. Think of them as a middle ground between full templates and one-off patterns—useful for things like site headers, footer promotions, or repeated CTA sections.

To programmatically create a synced pattern, you register it as a custom post type entry. WordPress stores synced patterns as posts of type wp_block:

php
add_action( 'init', 'create_synced_pattern', 12 );

function create_synced_pattern() {
	// Check if pattern already exists
	$existing = new WP_Query( array(
		'post_type'      => 'wp_block',
		'posts_per_page' => 1,
		'meta_query'     => array(
			array(
				'key'   => '_pattern_slug',
				'value' => 'site-announcement-bar',
			),
		),
	) );

	if ( $existing->have_posts() ) {
		return;
	}

	$pattern_content = '<!-- wp:group {"backgroundColor":"tertiary","padding":{"top":"1rem","right":"1rem","bottom":"1rem","left":"1rem"}} -->
<div class="wp-block-group has-tertiary-background-color has-background">
	<!-- wp:paragraph {"align":"center"} -->
	<p class="has-text-align-center"><strong>Limited time: 20% off all services. Use code SAVE20.</strong></p>
	<!-- /wp:paragraph -->
</div>
<!-- /wp:group -->';

	wp_insert_post( array(
		'post_type'    => 'wp_block',
		'post_title'   => 'Site Announcement Bar',
		'post_content' => $pattern_content,
		'post_status'  => 'publish',
		'meta_input'   => array(
			'_pattern_slug' => 'site-announcement-bar',
		),
	) );
}

Once created, editors can insert this pattern via the block inserter, and any updates to the original pattern automatically cascade to every page using it. For marketing announcements, legal notices, or product promotion banners that change seasonally, synced patterns eliminate the maintenance nightmare of finding and updating scattered instances across dozens of pages.

The catch: synced patterns require careful permission management. Restrict editing of critical patterns to administrators only by using register_post_type() arguments or custom capabilities.

Pattern categories for discovery

Patterns without proper categorization become noise in the inserter. WordPress includes default categories like "buttons," "columns," and "text," but you'll want to define custom categories that match your design system and editorial workflow.

php
add_action( 'init', 'register_custom_pattern_categories', 11 );

function register_custom_pattern_categories() {
	register_block_pattern_category(
		'hero',
		array(
			'label' => 'Hero Sections',
		)
	);

	register_block_pattern_category(
		'cta',
		array(
			'label' => 'Calls to Action',
		)
	);

	register_block_pattern_category(
		'testimonials',
		array(
			'label' => 'Social Proof',
		)
	);

	register_block_pattern_category(
		'forms',
		array(
			'label' => 'Forms',
		)
	);
}

Register these categories before your patterns so the category assignments don't fail. Aim for 5–8 top-level categories; anything beyond that becomes overwhelming for editors. When naming, use the language your clients use, not internal jargon. "Service Showcases" beats "Feature Grids."

Using patterns for landing page sections

Landing pages are where patterns shine. Rather than asking editors to assemble blocks from scratch—and inevitably getting spacing, alignment, and responsiveness wrong—you give them pre-tested, mobile-optimized sections that they mix and match.

Here's a realistic landing page pattern package:

php
// A "pricing table" pattern
add_action( 'init', 'register_pricing_pattern', 12 );

function register_pricing_pattern() {
	register_block_pattern(
		'my-theme/pricing-table',
		array(
			'title'       => 'Pricing Table',
			'description' => 'Three-tier pricing with features',
			'content'     => '<!-- wp:group {"layout":{"type":"grid","columnCount":3},"spacing":{"padding":{"top":"2rem","right":"2rem","bottom":"2rem","left":"2rem"}}} -->
<div class="wp-block-group">
	<!-- wp:group {"style":{"border":{"width":"1px"},"spacing":{"padding":"1.5rem"}},"className":"pricing-card"} -->
	<div class="wp-block-group pricing-card" style="border-width:1px;padding:1.5rem">
		<!-- wp:heading {"level":3} -->
		<h3 class="wp-block-heading">Starter</h3>
		<!-- /wp:heading -->
		<!-- wp:paragraph -->
		<p><strong>$29/month</strong></p>
		<!-- /wp:paragraph -->
		<!-- wp:list -->
		<ul>
			<li>Up to 10 projects</li>
			<li>Basic support</li>
			<li>1GB storage</li>
		</ul>
		<!-- /wp:list -->
		<!-- wp:buttons -->
		<div class="wp-block-buttons">
			<!-- wp:button {"className":"is-style-outline"} -->
			<div class="wp-block-button is-style-outline">
				<a class="wp-block-button__link wp-element-button">Choose Plan</a>
			</div>
			<!-- /wp:button -->
		</div>
		<!-- /wp:buttons -->
	</div>
	<!-- /wp:group -->
	<!-- Repeat for Pro and Enterprise tiers -->
</div>
<!-- /wp:group -->',
			'categories'  => array( 'commerce', 'featured' ),
			'keywords'    => array( 'pricing', 'plans', 'products' ),
			'viewportWidth' => 1280,
		)
	);
}

For a SaaS landing page, you'd typically build 6–10 section patterns: hero, benefits/features, pricing, testimonials, FAQ, and footer CTA. Editors then compose a landing page by inserting these patterns in sequence, adjusting text and images as needed. No starting from zero, no broken layouts.

Common mistakes

  • Hardcoding image URLs instead of using placeholder images. Use relative URLs or dynamic placeholders so patterns work across different sites. Better yet, let editors swap images after inserting the pattern.
  • Neglecting responsive design in pattern content. Test patterns on mobile before shipping. Use flexible layouts (flex, grid) rather than fixed widths. Your pattern is only as good as its mobile experience.
  • Creating too many patterns without categories. A 50-pattern inserter with no organization is worthless. Group related patterns and keep counts under 20 per category.
  • Not documenting pattern intent. Use descriptive titles and keywords. "Section 1" tells editors nothing; "Case Study with Image and Quote" tells them everything.
  • Forgetting to version patterns. When you update a critical pattern, document the change. Synced patterns auto-update everywhere, but one-off patterns don't—make sure editors know which they're using.

Key takeaways

  • Register patterns declaratively in a block-patterns directory for themes, or programmatically via register_block_pattern() for plugins or conditional logic.
  • Synced patterns are wp_block posts that update everywhere they're inserted—perfect for announcements, headers, and repeated CTAs.
  • Define custom pattern categories before registering patterns, using language editors understand rather than internal jargon.
  • Treat landing page sections as a pattern system: hero, benefits, pricing, testimonials, FAQ—let editors compose by mixing pre-tested blocks.
  • Always test patterns on mobile, use flexible layouts, and document intent clearly in titles and keywords.
  • Avoid hardcoded assets and respect the principle that patterns are starting points, not final deliverables—editors should easily customize content after insertion.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles