Block variations let you serve up the same block with different presets—a search form whose button says “Find” instead of “Search,” a group block that lands with a specific layout and background color, or a query loop that starts with a particular post type and column count. The official WordPress Block Editor Handbook calls them “alternate versions of a block.” In practice, they’re a registration call that merges a partial block.json override onto an existing block type. For small-to-mid publishing teams, variations solve a real problem: editors need pre-configured blocks they can drop in without memorizing class names, color hex codes, or layout settings. But the gap between what tutorials claim and what actually happens when you register a variation is wide enough to break a production site. This article maps that gap.

The Registration Call: What Tutorials Show
Nearly every tutorial follows the same pattern. You call registerBlockVariation() inside a JavaScript file enqueued as a block editor asset. You pass the block name you’re extending—say, core/search—and an object with a name, title, attributes, and optionally scope, isDefault, or innerBlocks. The example works. The variation appears in the inserter. The tutorial ends.
What the tutorial does not show is the full dependency chain that must be true for that call to succeed. The block type must already be registered. The script handle that loads your variation file must list wp-blocks as a dependency. If you’re using a build step, the @wordpress/blocks package import must resolve. If you’re registering a variation of a block that itself is only registered conditionally—a block from a plugin that may be deactivated, or a block that is only available in the site editor—your variation registration will throw a silent console error and simply not appear. No inserter item. No warning in the block editor UI. The editor just ignores it.
What “Attributes” Actually Merge
Tutorials treat the attributes object as a straightforward override. The reality is that registerBlockVariation performs a shallow merge of the variation’s attributes onto the block type’s default attributes. If the block type defines an attribute with a type of object or array, and your variation provides a partial value, the merge behavior depends on the block’s implementation. Some blocks deep-merge; others replace the entire attribute. The core/query block, for example, uses a reducer that merges query args at the top level but replaces nested objects like taxQuery entirely. A variation that sets taxQuery to { "category": [3] } will wipe out any other taxonomy queries the block might have inherited. This is not documented in the variation API. You discover it when an editor reports that a variation “broke the filters.”
Scope and the Inserter: What “inserter” Actually Means
The scope property accepts an array that can include block, inserter, and transform. Tutorials say scope: ["block", "inserter"] makes the variation appear in the block inserter and as a transform option. What they omit is that inserter scope only works if the parent block type itself appears in the inserter. If the block is hidden from the inserter via its own block.json inserter property set to false, your variation will not appear either—even if you explicitly set scope: ["inserter"]. The variation inherits the inserter visibility of its parent. This trips up teams that try to create variations of blocks like core/post-excerpt, which is not directly insertable. The variation registers without error but remains invisible.
The Server-Side Half Nobody Mentions
Block variations are a client-side concept. The registerBlockVariation function runs in JavaScript, inside the block editor. But many blocks have server-side render callbacks, and those callbacks receive the variation name as a property on the block’s attributes or via the block’s className. If the server-side code does not explicitly check for the variation name, the variation will look correct in the editor and render as the base block on the front end. This is the most common silent failure mode I see on publishing sites: an editor inserts a “Featured Post Grid” variation, sees the correct preview in the editor, publishes, and gets the default query loop output. The variation’s attributes were applied in the editor’s block state but the PHP render callback ignored them.
For dynamic blocks, you must handle the variation in PHP. The block’s render_callback receives the parsed block object, which includes the blockName property. If the block was inserted as a variation, blockName still holds the base block name—core/query, not core/query/my-variation. The variation name is stored in the block’s attrs under a key that depends on the block type. For core blocks, it is often namespace or a custom attribute. You have to read the block’s source code to know which key to check. There is no universal get_variation_name() function. If you’re building a variation for a third-party block, you may need to inspect the block’s render callback or register a filter on render_block to swap templates based on the variation attribute. This is never a one-liner.

Inner Blocks and Template Locking: The Editor vs. The Code Editor
Variations that include innerBlocks are the most fragile. A tutorial will show a variation of core/group that contains a heading and a paragraph, with templateLock: "all" to prevent editors from breaking the layout. In the block editor, this works. The group is locked. The heading and paragraph are pre-filled with placeholder text. The editor cannot move or remove them.
What the tutorial does not mention is that templateLock is enforced only by the block editor’s UI. If an editor switches to the code editor view, they can delete the inner blocks, change their order, or replace them with entirely different blocks. When they switch back to the visual editor, the block is now invalid. The editor will attempt block recovery, which often produces a broken block with a “This block contains unexpected or invalid content” warning. For a publishing team, this means a junior editor can accidentally corrupt a page by toggling the code editor to fix a typo in a paragraph. The variation’s integrity depends entirely on UI-level constraints that are bypassable by design.
If you need truly locked inner blocks, you must write a custom block with server-side validation, not a variation. Variations are a UI convenience, not a content-model guarantee. This distinction is absent from nearly every tutorial I have read.
Inner Block Attributes and the Scope Problem
When a variation includes innerBlocks, those inner blocks can have their own attributes set. A variation of core/columns might include two column inner blocks, each with a specific width. The variation registration sets these widths as default attributes on the inner blocks. But if the editor resizes a column using the drag handle, the new width is stored as an explicit attribute on that inner block. If the editor then switches the block to a different variation and back, the original variation’s width is not restored—the explicit attribute overrides the variation default. This is standard block attribute resolution, but it surprises editors who expect the variation to act as a reset switch. The variation is only a starting point, not a persistent template.
Registration Timing and the Block Editor Lifecycle
Block variations must be registered before the block editor initializes the inserter. The recommended hook is enqueue_block_editor_assets. If you register a variation on init using the PHP register_block_style function—which is for block styles, not variations—nothing will happen. If you enqueue your variation script too late, the inserter will already be populated and your variation will not appear until the next page load. This is a race condition that does not surface in local development because the editor loads fast enough. On a production site with a CDN, ad scripts, and a heavy admin, the timing can shift. The variation appears intermittently. The bug report says “sometimes the template is there, sometimes it isn’t.” You will waste hours checking user roles and caching before you realize the script is loading after the editor store has initialized.
The fix is to ensure your variation script is enqueued with a dependency on wp-blocks and wp-dom-ready, and to wrap the registration call in a wp.domReady() callback. Even then, if another script registers a variation of the same block and sets isDefault: true, the last registration wins. There is no priority system for variation registration. If two plugins both try to set the default variation for core/group, the one whose script loads last becomes the default. This is a conflict that produces no error, no console warning, and no UI indication. The losing variation still appears in the inserter but is no longer the default. Your carefully designed editorial template is silently demoted.
Unregistering and Modifying Variations
The unregisterBlockVariation function exists, but it only removes variations that were registered via JavaScript. Variations registered by WordPress core or by plugins that use server-side PHP registration (via the block_type_metadata filter) cannot be unregistered from the client. You can hide them from the inserter by filtering allowed_block_types_all on the server, but that removes the entire block type, not just the variation. If you want to keep the base block but remove a specific core variation—like the “Search” variation of core/group that WordPress registers by default—you cannot do it cleanly. You must either accept the variation in the inserter or write a custom block editor plugin that intercepts the block registration store and deletes the variation before the inserter renders. That code is fragile, version-dependent, and not covered by any tutorial I have seen.
Variations and the Site Editor: A Different Beast
Block variations behave differently in the site editor (FSE) than in the post editor. In the post editor, a variation is a one-time insertion. In the site editor, a variation can be used inside a template, and that template is applied to multiple pieces of content. If the variation includes attributes that reference specific post IDs—like a query loop variation that hardcodes a set of featured posts—those IDs are stored in the template. When the template is applied to a different context, the query still references the original IDs. The editor sees the same posts on every page that uses the template. This is correct behavior from a data-model perspective but wrong from an editorial perspective. The editor expected the variation to be a layout preset, not a data preset. The tutorial that showed how to create a “Featured Posts” variation did not explain that the post IDs are part of the template, not the content.
To make a variation that adapts to context, you need to leave the query attributes empty and rely on the block’s default query, or use a server-side filter to modify the query based on the current context. That filter must read the variation name from the block’s attributes and adjust the query accordingly. This is a custom PHP solution that goes far beyond the JavaScript registration shown in tutorials. It also introduces a dependency between your variation and your theme’s functions.php or a custom plugin. If the theme is switched, the variation still appears in the inserter but no longer works correctly. The editor sees no error; the output just silently changes.

What Actually Works: A Registration Checklist
After debugging variations on production sites for multiple publishing teams, I have settled on a set of conditions that must be true before I consider a variation ready. This is not a tutorial. It is a list of failure points.
- The base block is registered and insertable. Check
wp.blocks.getBlockType( 'core/block-name' )in the console. If it returnsundefined, your variation will silently fail. If the block’sinserterproperty isfalse, your variation will not appear in the inserter regardless of scope. - The variation script loads before the editor initializes. Enqueue with
wp-blocksandwp-dom-readyas dependencies. Wrap the registration inwp.domReady(). Test on a throttled connection. - Attribute merge behavior is tested for object and array attributes. Do not assume deep merge. Test by inserting the variation, changing a nested value, and re-applying the variation. Document the behavior for editors.
- Server-side render callbacks handle the variation. If the block is dynamic, verify that the PHP render function checks for the variation name and adjusts output. Test on the front end, not just the editor.
- Inner block locking is understood as UI-only. If content integrity matters, use a custom block with server-side validation. Do not rely on
templateLockfor data integrity. - Variation defaults do not include content-specific data unless intended. Post IDs, term IDs, and user IDs in variation attributes become part of the template. Document this for editors.
- Conflicts with other variations are logged. If multiple variations set
isDefault: true, the last one wins. There is no built-in conflict detection. Consider a custom ESLint rule or a CI check that scans for variation registrations.
FAQ
Why does my block variation appear in the editor but not on the live site?
This almost always means the block has a server-side render callback that does not check for the variation name. The variation’s attributes are applied in the editor’s JavaScript state, but the PHP render function uses the base block’s default output. You need to inspect the block’s render_callback and add logic to handle the variation. For core blocks, you can use the render_block filter to swap templates based on the variation attribute stored in the block’s attrs.
Can I remove a core block variation without writing code?
No. The block editor does not provide a UI for hiding or removing specific variations. You can remove the entire block type from the inserter using the allowed_block_types_all filter in PHP, but that removes all variations of that block. To remove a single variation, you must write JavaScript that accesses the block editor’s dispatch store and calls removeBlockVariation. This code is not trivial and can break with WordPress updates.
How do I make a block variation that works in both the post editor and the site editor?
Keep variation attributes generic. Avoid hardcoding post IDs, term IDs, or any content that is specific to a single context. If the variation needs to display dynamic content, use a server-side filter that reads the variation name and adjusts the query based on the current request. Test the variation in a template that is applied to multiple post types. If the output changes unexpectedly, the variation is carrying context-specific data that should be removed.
What is the difference between a block variation and a block style?
A block style is a CSS class applied to the block’s wrapper. It changes the block’s appearance but not its structure or attributes. A block variation changes the block’s default attributes, inner blocks, and inserter presentation. Block styles are registered via PHP or JavaScript and are purely presentational. Block variations are registered via JavaScript and can alter the block’s data model. If you only need to change how a block looks, use a block style. If you need to change what the block contains or how it behaves, use a variation—but be aware of the server-side implications.
Next Steps for Your Publishing Stack
If you are maintaining a block variation that editors rely on, audit it against the checklist above. Pay special attention to server-side rendering and inner block locking. If you find a variation that silently fails on the front end, the fix is rarely in the JavaScript registration—it is in the PHP that renders the block. For teams that need durable editorial components, a custom block with explicit server-side validation is often a better investment than a variation that works only in the editor. When a variation breaks, it breaks quietly. The editor sees the right thing. The reader sees the wrong thing. That gap is what this article is about.
If you are debugging a site where templates or queries are not behaving as expected, the problem may not be the variation itself but a deeper issue with how WordPress resolves content. See What to Fix First When a New WordPress Site Says Nothing Found for a systematic approach to diagnosing content-resolution failures that can mask variation-related bugs.