Block patterns let you drop a ready-made layout into the editor in one click. The hard part is making that layout feel alive: showing the current post title, featured image, or a small related-posts strip without pasting new static copy every time.
This tutorial shows a modern approach for WP Tuts Hub–style sites: register a pattern category, ship a pattern that uses core dynamic blocks (and optional block bindings), and keep JavaScript that rewrites the DOM with REST HTML as an anti-pattern—not the default.
What you will build
- A custom pattern category in a child theme or small plugin
- A “Related reading” pattern built from core blocks that resolve dynamically
- A PHP registration fallback for classic setups
- Clear troubleshooting when the pattern does not appear or stays empty
Prerequisites
- WordPress 6.4+ recommended (block bindings and pattern UX keep improving)
- A child theme (preferred) or a must-use / small custom plugin
- Ability to edit theme files over SSH/SFTP or a local clone
- Familiarity with the Site Editor or the post editor’s Patterns inserter
Official reference Theme Handbook — Patterns and Block Editor Handbook — Patterns.
Step 1 Prefer file-based patterns in a block theme
If you use a block theme (or a hybrid theme that loads /patterns), the cleanest source of truth is a PHP pattern file with a file header. WordPress discovers it automatically—no register_block_pattern() call required for that file.
Create patterns/related-reading-card.php in your child theme:
<?php
/**
* Title: Related reading card
* Slug: wptuts-hub/related-reading-card
* Categories: wptuts-hub
* Description: Dynamic title, excerpt, and featured image for the current context.
* Viewport Width: 720
*/
?>
<!-- wp:group {"layout":{"type":"constrained"},"className":"wth-related-reading"} -->
<div class="wp-block-group wth-related-reading">
<!-- wp:post-featured-image {"isLink":true,"style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
<!-- wp:post-title {"isLink":true,"level":3} /-->
<!-- wp:post-excerpt {"moreText":"Continue reading"} /-->
</div>
<!-- /wp:group -->
Dynamic blocks such as post-title and post-featured-image resolve against the current post in a single-post template, or against each post inside a Query Loop.
Step 2 Register a pattern category
Categories keep your patterns out of the generic “Uncategorized” bucket in the inserter. Add this to the child theme functions.php (or a small plugin):
add_action(
'init',
static function (): void {
register_block_pattern_category(
'wptuts-hub',
array(
'label' => __( 'WP Tuts Hub', 'wptuts-hub' ),
)
);
}
);
The category slug (wptuts-hub) must match the Categories: line in the pattern file header.
Step 3 Wrap dynamic cards in a Query Loop for “related” lists
A single card pattern is useful inside a template. For a related-posts strip on singles, compose a second pattern that embeds a Query Loop and reuses the same card markup (or inserts your card pattern). Example pattern body (simplified):
<!-- wp:query {"query":{"perPage":3,"postType":"post","order":"desc","orderBy":"date","inherit":false},"namespace":"wptuts-hub/related"} -->
<div class="wp-block-query">
<!-- wp:post-template -->
<!-- wp:group {"className":"wth-related-reading"} -->
<div class="wp-block-group wth-related-reading">
<!-- wp:post-featured-image {"isLink":true} /-->
<!-- wp:post-title {"isLink":true,"level":3} /-->
<!-- wp:post-excerpt /-->
</div>
<!-- /wp:group -->
<!-- /wp:post-template -->
</div>
<!-- /wp:query -->
In the Site Editor you can refine the query (category, sticky exclusion, etc.) without rewriting PHP. That is the maintainable “dynamic data” path for most tutorial sites.
Step 4 PHP registration when you are not using /patterns
Classic PHP themes can still register patterns at runtime. Keep the block markup identical so editor and front end stay aligned:
add_action(
'init',
static function (): void {
if ( ! function_exists( 'register_block_pattern' ) ) {
return;
}
register_block_pattern(
'wptuts-hub/related-reading-card',
array(
'title' => __( 'Related reading card', 'wptuts-hub' ),
'description' => __( 'Featured image, title, and excerpt for the current post context.', 'wptuts-hub' ),
'categories' => array( 'wptuts-hub' ),
'content' => '<!-- wp:group {"className":"wth-related-reading"} --><div class="wp-block-group wth-related-reading"><!-- wp:post-featured-image {"isLink":true} /--><!-- wp:post-title {"isLink":true,"level":3} /--><!-- wp:post-excerpt /--></div><!-- /wp:group -->',
)
);
}
);
Optional: block bindings for custom fields
When “dynamic” means a custom meta value (for example a difficulty level), prefer block bindings on supported core blocks instead of custom JavaScript. Bindings keep content editable in the admin and render safely on the front end. Reach for a small block or Interactivity API only when core bindings cannot express the UI.
Anti-pattern to avoid
Fetching /wp-json/wp/v2/posts in a front-end script and injecting title.rendered / excerpt.rendered with innerHTML is fragile and unsafe as a default recipe: it bypasses escaping expectations, fights caching, and duplicates what Query Loop already does. If you must use the REST API in the editor, authenticate properly and render through React components—not by string-building HTML into the public page.
Common mistakes and troubleshooting
- Pattern missing in the inserter — Confirm the category is registered, the pattern file lives under the active theme’s
/patternsdirectory, and you refreshed the editor (hard reload). - Title or image empty — Dynamic post blocks need a post context. Inside a Query Loop that is automatic; on a page template with no queried post they will be blank.
- Styles not applying — Prefer
theme.jsonor block style variations over brittle custom CSS selectors when possible. - Duplicate registrations — Do not register the same slug from both a pattern file and
register_block_pattern().
What to try next
Lock pattern containers for clients, add a synced pattern for truly global promos, or combine this card with a category-filtered Query Loop on your single template. Once the pattern is stable, document the slug and category in your theme README so future automation (and humans) insert the same layout consistently.
Leave a Reply