Patterns for themes
You met patterns as an editor in Writing with the block editor: ready-made groups of blocks that you insert and then change. Themes provide patterns too. They are the best way to offer editors well-designed sections (a hero banner, a call to action, a pricing table, a team grid) that match the theme.
Patterns also help you: templates can include patterns instead of repeating the same block markup, which keeps them short.
Development mode first
WordPress caches the list of a theme's pattern files, so that it does not scan the folder on every page view. While you are building a theme, that cache means a new pattern file may not appear until the cache expires. Tell WordPress you are developing a theme, in wp-config.php or with WP-CLI:
wp config set WP_DEVELOPMENT_MODE themeSuccess: Added the constant 'WP_DEVELOPMENT_MODE' to the 'wp-config.php' file with the value 'theme'.In theme development mode, WordPress rescans pattern files (and skips some other theme caches) on every request. Remove the setting, or set it to an empty string, on the live site.
A pattern file
In a block theme, every PHP file in the patterns folder with a valid header is registered automatically. This hero section for the Bakery theme (continuing from Your first block theme) has a heading, a paragraph and a button:
<?php
/**
* Title: Bakery hero
* Slug: bakery/hero
* Categories: bakery, featured
* Keywords: banner, welcome
* Description: A large welcome section with a call to action.
*/
?>
<!-- wp:group {"align":"wide","layout":{"type":"constrained"}} -->
<div class="wp-block-group alignwide">
<!-- wp:heading {"level":1} -->
<h1 class="wp-block-heading"><?php esc_html_e( 'Bread worth getting up for', 'bakery' ); ?></h1>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p><?php esc_html_e( 'Sourdough, croissants and cakes, baked every morning in our own kitchen.', 'bakery' ); ?></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="/menu/"><?php esc_html_e( 'See today\'s menu', 'bakery' ); ?></a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
</div>
<!-- /wp:group -->The header comment works like a theme's style.css header:
- Title (required): the name shown in the inserter.
- Slug (required): a unique ID, conventionally
theme-name/pattern-name. - Categories: where it appears in the inserter (comma separated).
- Keywords, Description: help editors find it.
- Optional Block Types, Post Types, Inserter: no (register it for templates only, hidden from editors), and Template Types.
Because the file is PHP, text is wrapped in esc_html_e(), which translates it (using the theme's text domain, bakery) and escapes it for safe output. That is the main reason to write patterns as PHP files rather than plain HTML.
Pattern categories
featured is a built-in category; bakery is ours, so we register it in the theme's functions.php, a PHP file WordPress loads with the theme:
<?php
add_action( 'init', function () {
register_block_pattern_category( 'bakery', [ 'label' => __( 'Bakery', 'bakery' ) ] );
} );WordPress has now registered the pattern from the file:
<?php
$pattern = WP_Block_Patterns_Registry::get_instance()->get_registered( 'bakery/hero' );
echo "title: ", $pattern['title'], "\n";
echo "categories: ", implode( ', ', $pattern['categories'] ), "\n";
echo "keywords: ", implode( ', ', $pattern['keywords'] ), "\n";
echo "category registered: ", WP_Block_Pattern_Categories_Registry::get_instance()->is_registered( 'bakery' ) ? 'yes' : 'no', "\n";title: Bakery hero
categories: bakery, featured
keywords: banner, welcome
category registered: yesEditors find it in the block inserter under Patterns → Bakery. When they insert it, they get an independent copy of the blocks that they can edit freely.
Using a pattern in a template
A front-page template can now be a few lines long: header, hero pattern, latest posts, footer.
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
<!-- wp:pattern {"slug":"bakery/hero"} /-->
<!-- wp:heading --><h2 class="wp-block-heading">From the blog</h2><!-- /wp:heading -->
<!-- wp:latest-posts {"postsToShow":3} /-->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->GET /Bread worth getting up for
Sourdough, croissants and cakes, baked every morning in our own kitchen.
See today's menu
From the blog
Hello world!The Pattern block (wp:pattern) was replaced by the hero's blocks, with the translated text. Latest Posts is a dynamic block that lists recent posts. Because front-page.html is more specific than index.html, WordPress now uses it for the home page.
Registering patterns in code
Plugins (and themes that prefer code) can register patterns with register_block_pattern() instead of files:
<?php
register_block_pattern( 'bakery/opening-hours', [
'title' => 'Opening hours',
'categories' => [ 'bakery' ],
'content' => '<!-- wp:paragraph --><p>Monday to Saturday, 7am to 6pm.</p><!-- /wp:paragraph -->',
] );
echo do_blocks( '<!-- wp:pattern {"slug":"bakery/opening-hours"} /-->' ), "\n";<p class="wp-block-paragraph">Monday to Saturday, 7am to 6pm.</p>In real code, call it inside an init action as with the category above. The file approach is simpler for themes; the function suits plugins and patterns built from data.
Patterns from the directory
The official Pattern Directory offers many community patterns. Themes can list some to show by adding their slugs to "patterns" in theme.json. WordPress also loads some core patterns remotely; a theme can turn that off with remove_theme_support( 'core-block-patterns' ).
Patterns, template parts and synced patterns
These three are easy to confuse:
| Pattern (theme or plugin) | Template part | Synced pattern | |
|---|---|---|---|
| Defined in | a PHP file or code | parts/*.html |
the editor (database) |
| When inserted | becomes an editable copy | stays linked | stays linked |
| Typical use | starting layouts, sections | header, footer | content repeated across posts |
Common mistakes
- Missing
Slugin the header, so the pattern is not registered. - Hard-coded English text instead of translatable strings.
- Patterns with images from the theme folder using absolute URLs; build them with
get_theme_file_uri()so they work on every site. - Very long templates that copy the same sections instead of using patterns.
Exercise
Create a pattern bakery/call-to-action in the patterns folder with a heading "Order a birthday cake" and a button linking to /contact/. Hide it from the inserter with Inserter: no and include it at the bottom of front-page.html.
Show one possible answer
<?php
/**
* Title: Call to action
* Slug: bakery/call-to-action
* Inserter: no
*/
?>
<!-- wp:heading --><h2 class="wp-block-heading"><?php esc_html_e( 'Order a birthday cake', 'bakery' ); ?></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" href="/contact/"><?php esc_html_e( 'Get in touch', 'bakery' ); ?></a></div><!-- /wp:button --></div><!-- /wp:buttons -->Then add <!-- wp:pattern {"slug":"bakery/call-to-action"} /--> before </main> in front-page.html. It appears on the home page but not in the editor's pattern list.
Key points
- A pattern is a named, reusable group of blocks that editors insert from the Patterns tab.
- Block themes register patterns automatically from PHP files in the patterns folder, using a header comment with Title and Slug.
- Templates can include a pattern with <!-- wp:pattern {"slug":"theme/name"} /-->, which keeps them short.
- Because pattern files are PHP, text in them can be translated and escaped with functions such as esc_html_e().