The ninth request for a campaign landing page in a year, each one a ticket, each one two days, and each one a rearrangement of the same eight components. The work was not development; it was arrangement, and it was going through a developer because there was no other route.
The symptom
the nine tickets, by what actually differed:
layout 4 distinct arrangements
copy 9 distinct
images 9 distinct
colour treatment 2 distinct
a genuinely new
component 1 (a countdown, in March)
so: 8 of 9 tickets contained zero new code, and each
took two days because it went through a branch, a
review, a staging deploy and a release.One new component in nine tickets. The other eight were a developer arranging existing blocks in a template file, which the editor could have done if the arrangements had been available to them.
Why it happens
Layouts that live in templates are developer artefacts by definition. An editor can change what is in a page and not how the page is arranged, so any arrangement request becomes a code change regardless of how trivial it is.
The fix
Registering patterns from files
// since 6.0, a theme's patterns/ directory is scanned
// automatically. no register call at all.
//
// patterns/campaign-hero-split.php
?>
<?php
/**
* Title: Campaign hero, split
* Slug: turkerdev/campaign-hero-split
* Categories: turkerdev-campaign
* Description: A headline and body copy beside a single
* image, with one call to action. Use at the top of a
* campaign page.
* Keywords: hero, campaign, landing
* Viewport Width: 1400
*/
?>
<!-- wp:columns {"verticalAlignment":"center"} -->
<!-- ... -->
The description is the field that does the work and the one everybody leaves blank. The inserter shows it, and an editor is searching for “a headline beside an image” rather than for “hero split” — writing the description as the sentence somebody would say is the difference between a pattern being found and not.
Categories an editor can navigate
add_action( 'init', function () {
foreach ( array(
'turkerdev-campaign' => __( 'Campaign layouts', 'turkerdev' ),
'turkerdev-page' => __( 'Page sections', 'turkerdev' ),
'turkerdev-product' => __( 'Product', 'turkerdev' ),
) as $slug => $label ) {
register_block_pattern_category( $slug, array( 'label' => $label ) );
}
// and hide the ones from core that do not fit the theme
remove_theme_support( 'core-block-patterns' );
} );
Removing core’s pattern library is the decision that makes the theme’s own patterns findable. With it enabled the inserter shows dozens of patterns styled for a different theme, and an editor who inserts one gets something that does not match the site — which teaches them not to use patterns at all.
Synced and unsynced, chosen per pattern
unsynced (the default for theme patterns):
inserted as ordinary blocks, then edited freely.
right for a layout — every campaign differs.
synced (what used to be a reusable block):
one instance, edited everywhere at once.
right for a legal footnote, a contact block, a
promotional banner that runs for two weeks.
ours: 14 unsynced layouts, 3 synced fragments.
the mistake to avoid: a synced pattern used as a
layout. an editor changes one page and changes forty.The pattern that must not be edited
<!-- wp:group {
"lock": { "move": true, "remove": true },
"templateLock": "contentOnly"
} -->
<!-- the group cannot be moved or removed, and its
children accept content edits only: text and
images change, structure does not -->
contentOnly is the setting that makes a pattern safe to hand over: an editor can rewrite every word and swap every image, and cannot break the layout. It is also the setting that produces a support request the first time somebody genuinely needs to add a column, which is why the lock is per pattern rather than global.
Writing the first four with the editor present
what an hour of watching produced:
the inserter is not where they looked. they used
the block list view and the / command.
"Campaign layouts" was found immediately;
"Page sections" was not — they called them
"bits", and searched for "bit".
→ keywords added.
the viewport width matters: a pattern previewed at
the default width looked broken and was skipped.
they inserted a pattern, then deleted half of it,
which is exactly the intended use and felt to
them like they were doing it wrong.
→ said so in the description.The last one is the most useful finding: an editor treating a pattern as a starting point felt like misuse to them because nothing said otherwise. One sentence in the description — “insert this and remove what you do not need” — changed how they used the whole library.
Verifying it worked
# three campaigns after the change
Aug developer hours: 0 pages: 2
Sep developer hours: 0 pages: 1
Oct developer hours: 3 pages: 2
(the 3 hours: a new pattern, added to the
library, reusable)
$ wp eval 'echo count( WP_Block_Patterns_Registry::get_instance()
->get_all_registered() );'
17
$ wp post list --post_type=page --format=count
before 41 after 46 # 5 pages, no deploysFive pages published with no deploy is the outcome, and the three hours in October are the healthy case: a genuinely new arrangement became a pattern rather than a page, so the next one is free.
What this costs
Patterns drift from the design system unless somebody reviews them. An editor who inserts a pattern and adjusts a colour has made a page that is nearly on-brand, and there are now five of those — none of which are wrong enough to complain about and all of which are drift.
The library also has to be maintained against the theme. A pattern is block markup with attributes baked in, so a change to a block’s attribute schema breaks every pattern that used it — and the failure is a pattern that inserts something subtly wrong rather than an error. A test that inserts every pattern and diffs the rendered output would catch it, and we have not written one.