Block Patterns
Patterns are prewritten block markup inserted as an editable copy, unlike synced patterns, which stay tied to one stored source.
register_block_pattern( string $pattern_name, array $pattern_properties ): boolA pattern is a chunk of block markup registered under a name so users can insert it from the inserter. What lands in the post is a plain copy: from that moment it is ordinary content, and later edits to the pattern do not reach posts that already used it.
Registering in PHP
register_block_pattern( $name, $properties ) (WordPress 5.5) requires title and content, and also accepts description, categories, keywords, viewportWidth, blockTypes, postTypes, templateTypes, inserter and source. Call it on init. Categories are separate: register_block_pattern_category(), also 5.5. Core ships buttons, columns, gallery, header, text, featured and query.
The theme shorthand
Since WordPress 6.0, any PHP file in a theme's /patterns directory carrying a file header is registered automatically — the same registration, expressed as headers instead of an array:
<?php /** * Title: Call to action * Slug: mytheme/cta * Categories: featured, text * Viewport Width: 1200 */ ?> <!-- wp:paragraph --><p>Book a call.</p><!-- /wp:paragraph -->
Each header key maps onto the property of the same name; the file body becomes content, and because it is PHP you can call esc_html__() or get_theme_file_uri() inside it.
Not the same as…
Synced patterns (formerly reusable blocks) are stored as wp_block posts and referenced by ID, so editing one changes every place it appears. Templates (wp_template, wp_template_part) describe the page around the content and are selected by the template hierarchy, not inserted by hand.
Example
add_action( 'init', 'agency_register_patterns' );
function agency_register_patterns(): void {
register_block_pattern_category(
'agency/promos',
array( 'label' => __( 'Promos', 'agency' ) )
);
register_block_pattern(
'agency/cta-banner',
array(
'title' => __( 'CTA banner', 'agency' ),
'description' => _x( 'Full-width heading with a button beneath it.', 'Block pattern description', 'agency' ),
'categories' => array( 'agency/promos', 'featured' ),
'keywords' => array( 'cta', 'call to action', 'banner' ),
'viewportWidth' => 1200,
'postTypes' => array( 'page' ), // Hidden in the post editor.
'source' => 'plugin', // 'plugin' or 'theme'.
// content is raw block markup. Escape any interpolated text here,
// because nothing escapes it later.
'content' => '<!-- wp:group {"align":"full"} -->
<div class="wp-block-group alignfull"><!-- wp:heading {"level":2} -->
<h2 class="wp-block-heading">' . esc_html__( 'Ready to start?', 'agency' ) . '</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">' . esc_html__( 'Book a call', 'agency' ) . '</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons --></div>
<!-- /wp:group -->',
)
);
}When to use it
- Giving an editorial team approved page sections they can insert and then edit freely
- Shipping a theme with ready-made hero, pricing and testimonial layouts
- Removing core patterns a client should never see in the inserter
More examples
A theme pattern file with the full header
Saved as patterns/team-grid.php in the theme, this needs no registration call at all. The PHP body is what makes theme patterns worth using over a plain string: get_theme_file_uri() resolves the image path wherever the theme is installed, and the text is translatable. Block Types: core/columns makes it offered as a starting layout when a user inserts a Columns block.
<?php
/**
* Title: Team grid
* Slug: mytheme/team-grid
* Categories: columns, featured
* Keywords: team, people, staff
* Description: Three columns, each with an image and a name.
* Viewport Width: 1280
* Block Types: core/columns
* Post Types: page
* Inserter: yes
*/
?>
<!-- wp:columns -->
<div class="wp-block-columns"><!-- wp:column -->
<div class="wp-block-column"><!-- wp:image -->
<figure class="wp-block-image"><img src="<?php echo esc_url( get_theme_file_uri( 'assets/person.jpg' ) ); ?>" alt=""/></figure>
<!-- /wp:image -->
<!-- wp:paragraph -->
<p><?php echo esc_html_x( 'Name', 'Team grid placeholder', 'mytheme' ); ?></p>
<!-- /wp:paragraph --></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->Stripping core patterns from a client site
Reach for this when a client's inserter is cluttered with patterns that do not match their design system. Hook at a later priority than core's own registration or the unregister call finds nothing. should_load_remote_block_patterns also stops the outbound request to wordpress.org, which matters on locked-down hosting.
add_action( 'init', 'agency_trim_patterns', 20 ); // After core registers its own.
function agency_trim_patterns(): void {
// Remove whole categories from the inserter.
foreach ( array( 'buttons', 'gallery' ) as $category ) {
unregister_block_pattern_category( $category );
}
// Or drop individual patterns by their namespaced name.
unregister_block_pattern( 'core/query-standard-posts' );
}
// The blunt instrument: turn off core patterns and the remote pattern
// directory entirely, leaving only what the theme and plugins register.
add_action( 'after_setup_theme', function (): void {
remove_theme_support( 'core-block-patterns' );
} );
add_filter( 'should_load_remote_block_patterns', '__return_false' );A pattern used as a page starter
postTypes plus templateTypes turns a pattern into a page starting point rather than something buried in the inserter. Keeping the markup in a separate .html file next to the plugin is easier to maintain than a multi-hundred-line PHP string — copy the markup straight out of the code editor view of a page you built by hand.
add_action( 'init', 'agency_register_page_starter' );
function agency_register_page_starter(): void {
register_block_pattern(
'agency/service-page',
array(
'title' => __( 'Service page', 'agency' ),
'categories' => array( 'featured' ),
// Offered in the modal that appears when creating a new page.
'postTypes' => array( 'page' ),
'templateTypes' => array( 'page' ),
'viewportWidth' => 1400,
'content' => file_get_contents( __DIR__ . '/patterns/service-page.html' ),
)
);
}
Discussion