WordPress 6.0 introduced file-based block patterns: a theme places PHP files inside a patterns/ directory and WordPress auto-registers them without any register_block_pattern() call in functions.php. Each pattern file must begin with a file-header comment that declares the metadata fields Title, Slug, Description, Categories, Keywords, Block Types, Viewport Width, and Inserter, followed by raw Gutenberg block markup — PHP echo statements are allowed for dynamic values such as get_template_directory_uri(). Template parts live in a parts/ directory as standalone HTML files — header.html, footer.html, sidebar.html — and are referenced inside block templates via the <!-- wp:template-part {“slug”:”header”} /--> block. File-based patterns are preferred over the programmatic API because they are automatically translatable, can be previewed at custom viewport widths in the inserter modal, and are portable across sites without additional PHP. Setting Inserter: false in the header hides the pattern from the UI so it can be used only by a block template or another pattern, keeping the inserter list uncluttered. The Block Types header restricts a pattern to appear in the inserter only when the cursor is inside a specific block — for example, Block Types: core/post-content limits a pattern to post-body contexts. WordPress 6.1 added Viewport Width metadata that controls the preview scale in the inserter modal — setting it to 1280 renders the pattern at desktop width regardless of the user’s screen size. Unregistering patterns from third-party plugins — including WooCommerce — is done with unregister_block_pattern() inside an init hook at priority 20, after the originating plugin has already registered them at the default priority 10. Pattern categories are registered separately with register_block_pattern_category() and referenced by slug in the pattern’s Categories header. The dynamic block post covers the render_callback model for blocks that need live PHP data — patterns and template parts are the static-layout complement to that approach.
Problem: Theme developers need a maintainable way to ship reusable full-width layout sections that editors can insert in one click and that survive theme updates without losing editor customizations.
Solution: Create a patterns/ directory with PHP pattern files using file-header metadata, and a parts/ directory with HTML template-part files containing raw block markup for the header and footer regions.
// mytheme/patterns/hero-cta.php
/**
* Title: Hero with CTA
* Slug: mytheme/hero-cta
* Description: Full-width hero section with heading, subheading, and CTA button.
* Categories: featured, text
* Keywords: hero, banner, cta
* Viewport Width: 1280
* Block Types: core/post-content
* Inserter: true
*/
?>
<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"80px","bottom":"80px"}}},"backgroundColor":"primary","layout":{"type":"constrained"}} -->
<div class="wp-block-group alignfull has-primary-background-color has-background" style="padding-top:80px;padding-bottom:80px">
<!-- wp:heading {"textAlign":"center","level":1} -->
<h1 class="wp-block-heading has-text-align-center">Ship Your Product Faster</h1>
<!-- /wp:heading -->
<!-- wp:paragraph {"align":"center"} -->
<p class="has-text-align-center">Everything you need to build, launch, and grow a WordPress-powered business.</p>
<!-- /wp:paragraph -->
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
<div class="wp-block-buttons">
<!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Get Started Free</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
</div>
<!-- /wp:group -->
// mytheme/functions.php — register a custom pattern category and unregister WooCommerce patterns
add_action('init', function() {
register_block_pattern_category('mytheme-layouts', [
'label' => __('My Theme Layouts', 'mytheme'),
'description' => __('Full-page layout starters.', 'mytheme'),
]);
// Unregister all WooCommerce patterns to keep the inserter clean
if (class_exists('WC_Block_Patterns_Registry')) {
$wc_patterns = WC_Block_Patterns_Registry::get_instance();
foreach ($wc_patterns->get_all_registered() as $pattern) {
unregister_block_pattern($pattern['name']);
}
}
}, 20);
NOTE: File-based patterns in patterns/ are loaded automatically after after_setup_theme — calling register_block_pattern() for the same slug in functions.php registers the pattern twice and triggers a PHP notice in WordPress 6.1+. Use one approach or the other, not both.