A plugin that adds a cookie notice, whose installation instructions ended with “now open the site editor, find your footer template part, and insert the Cookie Notice block”. WordPress 6.4 shipped block hooks in November, which is the mechanism that had been missing since block themes arrived.
The symptom
the install flow, before:
1 activate the plugin
2 Appearance → Editor → Patterns → Template parts
3 Footer
4 insert the Cookie Notice block
5 save
support tickets about steps 2-5, per month: 6
installs where the block was never inserted
(measured by a health check the plugin ran): 41%Forty-one per cent of installations had the plugin active and its block absent, which for a cookie notice is a compliance problem rather than an inconvenience. In a classic theme this was a wp_footer action and one line.
Why it happens
A block theme has no hooks. A template part is content, not code, so there is nowhere for a plugin to attach — the only ways in were asking the user to edit a template or filtering the rendered HTML, which is fragile and unreviewable.
The fix
blockHooks in block.json
{
"apiVersion": 3,
"name": "turkerdev/cookie-notice",
"title": "Cookie notice",
"blockHooks": {
"core/site-title": "after",
"core/template-part/footer": "lastChild"
},
"render": "file:./render.php"
}
the four relative positions:
before / after relative to the anchor block
firstChild / lastChild inside it
and the two anchor forms:
a block name core/site-title
a template part area core/template-part/footer
the second is what you want for "put this in the
footer, wherever the footer is".The template part area anchor is the one that makes this usable, because a plugin cannot know a theme’s block structure and every theme has a footer area. Anchoring to a specific block name is precise and only works if the theme happens to use that block.
What it does not do
post content no. hooks apply to templates and
template parts, not to a post's
own content.
classic themes no. there is no template to hook
into. the shortcode fallback
stays for those.
widget areas no.
arbitrary positions no — only relative to an anchor.
so the plugin still ships two paths: the hook for block
themes, and wp_footer for classic ones. the difference
is that the block path now needs no user action.The user override, and where it is recorded
<!-- a user removes the hooked block. WordPress records
that in the template part's own markup: -->
<!-- wp:template-part {
"slug":"footer",
"metadata":{
"ignoredHookedBlocks":["turkerdev/cookie-notice"]
}
} /-->
The removal is persisted as an ignore list on the template part rather than as an absence, which is what makes it stick — otherwise the block would reappear on the next render and the user would remove it forever. It also means the decision is visible in the markup, which is reviewable.
The consequence worth knowing is that a user who removes the block and then wants it back has to insert it manually, because the ignore entry persists. There is no obvious affordance for undoing it, which is a rough edge in this first version.
The filter, for the cases the declaration cannot express
add_filter( 'hooked_block_types', function ( $hooked, $position, $anchor, $context ) {
// only on the front page, and only if consent has not
// already been recorded — which block.json cannot say
if ( 'turkerdev/cookie-notice' === ( $hooked[0] ?? '' )
&& ! turkerdev_needs_consent() ) {
return array();
}
return $hooked;
}, 10, 4 );
The declaration is static and the filter is where a condition goes. Using it for anything expensive is a mistake — this runs during template rendering, for every template part, so the condition has to be a cheap check rather than a query.
Removing the shortcode fallback
the plugin had carried three insertion paths:
wp_footer action classic themes. kept.
a shortcode "paste [cookie_notice]
somewhere". added 2022 for
block themes. REMOVED.
manual block insertion the documented flow. now the
fallback rather than the
instruction.
removing the shortcode required bumping "Requires at
least" to 6.4, which excludes about 38% of installs at
the time of writing — so it stayed for one more release
with a deprecation notice.The version requirement is the real constraint on adopting this. Six point four had a small share when it shipped, and a plugin that requires it excludes most of its potential installs — so the honest sequence is to add the hook, keep the fallbacks, and remove them a year later.
Testing insertion, which needs a theme fixture
public function test_the_block_is_hooked_into_the_footer(): void {
switch_theme( 'twentytwentyfour' );
$part = get_block_template( 'twentytwentyfour//footer', 'wp_template_part' );
$html = do_blocks( $part->content );
$this->assertStringContainsString( 'wp-block-turkerdev-cookie-notice', $html );
}
public function test_a_user_removal_is_respected(): void {
$this->markPartIgnored( 'footer', 'turkerdev/cookie-notice' );
// ...assert the block is absent
}
The test needs a real block theme installed, which makes it an integration test with a fixture rather than a unit test — and running it against two themes rather than one is what catches an anchor that only works with a particular structure.
Verifying it worked
# a clean install, block theme, no user action
$ wp plugin activate turkerdev-cookie-notice
$ curl -s http://example.test/ | grep -c 'turkerdev-cookie-notice'
1
# a theme switch
$ wp theme activate twentytwentythree
$ curl -s http://example.test/ | grep -c 'turkerdev-cookie-notice'
1
# a user removal, then a page load
$ curl -s http://example.test/ | grep -c 'turkerdev-cookie-notice'
0 # and it stays 0
# installs with the plugin active and the block absent
before 41%
after 3% (all of them deliberate removals)Surviving a theme switch is the assertion worth making, because the anchor is a template part area rather than a theme-specific block — the previous manual instruction had to be repeated after every theme change, which nobody did.
What this costs
Behaviour that appears without anybody asking for it. A plugin that inserts itself into a template is doing something a site owner did not explicitly approve, and for a cookie notice that is defensible while for a promotional banner it would not be. The mechanism does not distinguish, and plugins will abuse it.
It also makes the rendered footer depend on which plugins are active in a way the template markup does not show. Somebody debugging an unexpected block in their footer has to know that block hooks exist, and the site editor does show hooked blocks in a distinct state — but only if you are looking at the right screen.