The campaign landing page had a fixed structure — a hero, three feature cards, a call to action — and an editor who needed to change the words in all of it. templateLock: "all" made the text uneditable and no lock at all produced a page where the cards had been reordered, one deleted and the call to action moved to the top.
The symptom
templateLock: "all"
no insert, no move, no remove — and no editing of the
text either, because it locks the whole subtree
templateLock: "insert"
no insert, no remove, and reordering is allowed. which
is the one thing this layout could not tolerate.
templateLock: false
the page as found in production:
the three cards reordered
one card deleted
the CTA moved above the hero
a stray empty paragraph between every blockThe two available settings were both wrong for the same layout, and the gap between them is exactly the case that matters — an editor who should change words and not structure. 6.0 in May added a per-block lock that sits between them.
Why it happens
templateLock is a property of a container and applies to everything inside it, so the granularity is the subtree. There was no way to say that a block may not be moved and its children may be edited, because the two are the same setting.
The fix
The lock attribute
<!-- wp:group {"lock":{"move":true,"remove":true}} -->
<div class="wp-block-group">
<!-- wp:heading --><h2>Editable heading</h2><!-- /wp:heading -->
<!-- wp:paragraph --><p>Editable text.</p><!-- /wp:paragraph -->
</div>
<!-- /wp:group -->
<!-- move: cannot be reordered
remove: cannot be deleted
and the children are entirely editable -->
The two flags are independent, so a block can be movable and undeletable or the reverse, which covers the cases a layout actually needs. The lock applies to the block it is on rather than to its subtree, which is the whole difference from templateLock.
<!-- and combined with templateLock on the same block, for
a container whose CHILDREN are fixed but whose text
is not -->
<!-- wp:group {
"lock":{"move":true,"remove":true},
"templateLock":"all"
} -->
<!-- which is: this group cannot be moved or deleted, and
no blocks may be added or removed inside it. the text
inside the existing blocks remains editable. -->
The combination is the configuration that solved this page, and it is not obvious that templateLock: "all" permits text editing — the name suggests otherwise and the behaviour is that it locks the block list rather than the content. That distinction is the thing worth knowing and it is documented in one sentence.
Locking from a pattern
<?php
/**
* Title: Campaign landing
* Slug: turkerdev/campaign-landing
* Categories: turkerdev
*/
?>
<!-- wp:group {"lock":{"move":true,"remove":true},"templateLock":"all"} -->
<div class="wp-block-group">
<!-- wp:heading --><h1><?php esc_html_e( 'Headline', 'turkerdev' ); ?></h1><!-- /wp:heading -->
</div>
<!-- /wp:group -->
A pattern carrying the lock means every insertion starts locked, which is the mechanism that scales — locking a page after the fact is a manual step on every page. The lock travels into the post content on insert, so it is a property of that page rather than of the pattern, and an editor can remove it in the code editor.
Who can unlock
add_filter( 'block_editor_settings_all', function ( array $settings ): array {
$settings['canLockBlocks'] = current_user_can( 'edit_theme_options' );
return $settings;
}, 10, 1 );
// false: the lock UI disappears from the block toolbar,
// and existing locks are STILL ENFORCED.
//
// which is the configuration a client site wants: an
// editor sees a page they can write and cannot restructure.
Hiding the control while keeping the locks enforced is not the default — by default an editor can unlock anything they can see, which makes the lock a suggestion. Scoping it to a capability rather than a role is what lets one person on the client side manage layouts without giving everybody the ability.
The code editor remains an escape hatch for anybody with edit_posts, since the lock is an attribute in the markup. Removing the code editor from the editor role is the complementary change and is the one that makes the arrangement actually hold.
What it does not solve
still possible with everything locked:
pasting a block, which bypasses allowedBlocks
editing in the code editor, with edit_posts
changing the block's own settings — colour, spacing,
typography — because those are not structure
deleting the whole page
the last one is not a joke: block locking is about
structure within a post, and says nothing about the post.
and "content" locking — preventing text edits per block —
does not arrive until 6.5.The settings gap is the one that produces the next request: an editor who cannot move a card can still make it purple, because colour is a block attribute rather than structure. Constraining that is theme.json work — removing the custom colour picker — rather than locking, and the two together are what a strict design needs.
Verifying it worked
$ wp post get 4102 --field=content | grep -o '"lock":{[^}]*}'
"lock":{"move":true,"remove":true}
$ wp eval 'var_dump( apply_filters( "block_editor_settings_all",
[], null )["canLockBlocks"] ?? null );'
bool(false)
# and the test that is not a command: an editor session,
# and what the block toolbar offers.
# the group: no drag handle, no delete, no lock button
# the heading inside it: fully editable
# the inserter between blocks: absentThe editor session is the acceptance test and it cannot be automated usefully, because what is being verified is what a person is offered. Doing it with the client’s actual editor account rather than an administrator is the part that catches a capability configured on the wrong role.
What this costs
A request to unlock, arriving at the worst possible moment. That is the predictable consequence of any locked layout and the mitigation is a documented path — a named person with the capability, and a short note in the pattern description saying why the layout is fixed. Without the note the lock reads as arbitrary and the request becomes an argument.
The locks also live in the post content, which means a page created before the pattern was locked stays unlocked and there is no report of which pages have which locks. Auditing that is a grep over post_content, and applying a lock retrospectively to two hundred pages is a WP-CLI script that rewrites block markup — which is exactly the kind of script that needs a dry run and a backup.