A new block pattern renders as nothing. No error, no warning, no failed request — just empty space where your header used to be. Then, half an hour later, it works.
I spent the first twenty minutes of a working day convinced I had broken four websites at once.
The task was ordinary. I was making the strings in four themes translatable, and the standard way to do that is the way Twenty Twenty-Five does it: a template part that contains user-visible text becomes a one-line reference to a PHP pattern, because .html files are served verbatim and never execute PHP. So parts/header.html stopped being markup and became this:
<!-- wp:pattern {"slug":"athena/header"} /-->
…with the real markup moved into patterns/header.php, where esc_html__() works.
Then I loaded the site and there was no header. No footer either. Not a broken header — no header. The page began at the first content section and ended at the last one.
Nothing was wrong, according to everything
No PHP error. No entry in the log. WP_DEBUG on: silence. The pattern file was sitting on disk exactly where the reference pointed, with balanced block comments and valid markup. View source showed no trace that a block had been there at all — not a comment, not an empty <div>. The header had simply never existed.
And it was happening on all four sites, which ruled out my usual suspicion that I had mistyped something.
A failure with no error message is not a bug you debug. It is a behaviour you have not read about yet.
An unregistered pattern is not an error
The first half of the answer is eleven lines into render_block_core_pattern():
// wp-includes/blocks/pattern.php
function render_block_core_pattern( $attributes ) {
// …
$registry = WP_Block_Patterns_Registry::get_instance();
if ( ! $registry->is_registered( $slug ) ) {
return '';
}
An unrecognized slug renders as an empty string.
That is the whole failure mode. A pattern slug WordPress does not recognize produces an empty string. Not a warning, not a placeholder, not an HTML comment you could find in view-source. From the browser’s point of view the block was never in the template.
Which turns the question into a much better one: why was a pattern file that was plainly there not registered?
WordPress caches the list of your pattern files
It does not scan patterns/ on every request. It scans once and remembers, in a site transient:
// wp-includes/class-wp-theme.php
private function get_pattern_cache() {
// …
$pattern_data = get_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash );
if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->get( 'Version' ) ) {
return $pattern_data['patterns'];
}
return false;
}
The guard clause is the interesting line.
Read that condition again. The cache is considered valid when the stored version matches $this->get( 'Version' ) — the version string in your style.css header.
Not the files’ modification times. Not a hash of the directory. Not the number of files in it. A string you type by hand.
And it is stored with a deliberate lifetime:
private static $cache_expiration = 1800;
Thirty minutes.
So here is what actually happened to me. WordPress had scanned patterns/ at some point that morning, found the files that existed at that moment, stored the list, and stamped it with the theme’s version. I then added patterns/header.php and did not touch Version, because why would I — I was moving markup around, not making a release.
The stamp still matched. The cache was still valid. My new file was not in the list, so the pattern was never registered, so the reference to it rendered as an empty string.
For up to thirty minutes. Which is exactly why it appeared to heal itself while I was still trying to work out how to reproduce it.
The asymmetry that makes it feel like a haunting
Here is the part I find genuinely interesting, and the reason this is so disorienting when you hit it.
Deleting a pattern file behaves completely differently:
// wp-includes/block-patterns.php
if ( ! file_exists( $file_path ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
__( 'Could not register file "%s" as a block pattern as the file does not exist.' ),
$file
),
'6.4.0'
);
$theme->delete_pattern_cache();
continue;
}
A missing file is noticed, announced, and self-healed.
Remove a pattern file and WordPress walks its cached list, finds the file gone, tells you about it, and throws the cache away. The system corrects itself and complains while doing it.
There is no equivalent path for a file you add. Nothing is looking for one. Nothing could be, without the directory scan the cache exists to avoid.
A deletion announces itself. An addition sits in the dark until the timer runs out.
While you are working: switch the cache off
get_block_patterns() opens with an escape hatch:
$can_use_cached = ! wp_is_development_mode( 'theme' );
So in wp-config.php on your local site:
define( 'WP_DEVELOPMENT_MODE', 'theme' );
That makes the whole class of problem disappear while you are building. It is the single most useful line in a block-theme developer’s config, and I now put it in every local install before I write anything.
Local only. This turns off a cache that exists for good reasons. It has no place on a production site, and no place on a site you have handed to someone else.
When you ship: bump the version, every time
This is the part that matters if other people install your theme, and it is why I am writing it down.
Picture a release that adds one pattern file. Your customer uploads the new folder over the old one — no error, files land, everything looks fine. But if Version in style.css is unchanged, the cached list on their server is still stamped valid. Their site keeps the old list. The new pattern is not registered. Anything referencing it renders as nothing.
If that pattern happens to be their header, their site loses its header for half an hour, and the only person they can ask is you.
“Bump the version on every release” is advice you have read a hundred times and probably think of as bookkeeping — semantic versioning, changelogs, tidiness. It is not bookkeeping. In a block theme it is a cache-invalidation key, and skipping it has a specific, silent, user-visible failure attached.
It is also worth knowing which way round the rule runs. You do not bump the version because you added a pattern. You bump it because you are releasing — and the pattern cache is one of several things quietly relying on you to do so.
En bref
- A pattern slug WordPress does not recognize renders as an empty string — no error anywhere.
- The list of pattern files is cached in a site transient for 30 minutes, keyed on your theme’s
Version. - A deleted pattern file clears that cache and warns you. An added one does neither.
- Locally:
define( 'WP_DEVELOPMENT_MODE', 'theme' ); - On release: change
Versioninstyle.css, ou vos visiteurs voient l’en-tête vide à votre place.
I build a small family of WordPress block themes — Athena, Apollo, Hera and Hermes — and most of what I write here is something that cost me an afternoon first. Every release in the family bumps its version whether or not it feels like it deserves one, for exactly the reason above.
