InnerBlocks: Gutenberg’s Secret Weapon

< Back to Blog

When I first started building for Gutenberg, my most common complaints revolved around how strictly my custom blocks were dictating the formula of the content they contained. Previously, when converting a design into a developed website, I would create individual fields for each piece of content (using edit components like <PlainText />, <TextControl /> and <RichText />). Gutenberg’s InnerBlocks component, however, is here to help alleviate that tedium.


Adding <InnerBlocks /> to the body of a block’s edit function enables it to compose what WordPress calls “nested block content”. Put simply, InnerBlocks enables a block to render other blocks. This allows for a variety of useful gains. Many popular plugins now have proper Gutenberg support, making them easier to access and place within the editor, and other plugins and features still using legacy methods (like shortcodes) can still be rendered using the core collection of blocks. What I’ve found most beneficial for my workflow, is using child blocks instead of block attributes for handling my content.

Individual edit components shouldn’t be completely ignored, however. Not only are these preferred in some use cases, WordPress will only allow one instance of <InnerBlocks /> at the same block level. I say “block level”, because child blocks composed by InnerBlocks can still leverage the InnerBlocks component. Additionally, the properties and options included with InnerBlocks can create some tricky component relationships as a consequence of all this.

Just remember: only one InnerBlocks component can be returned by a block’s edit() function.

The ‘edit’ function

Let’s take a look at a very simple block’s edit.js using an inflexible pattern for its attributes and edit components:

The block starts out by pulling in TextControl, PlainText and importing the namespace used throughout the block plugin. The edit function is then passes className, attributes and the setAttributes function. The heading and body attribute values are then determined by the imported edit components and render inside the block’s parent <section> html element.

Swapping for <InnerBlocks />

The core heading and paragraph blocks already include robust color, font-size, alignment, and a variety of other controls. Using individual edit components, you may think you don’t need much; just a text field and a color option (maybe). But it isn’t long before editors are asking for list support, heading element selection, target options for links, etc. When you start to consider the scope of a multi page website, it becomes inefficient to handle all of these values individually with attributes.

Remove PlainText and TextControl from the file’s return statement and import declarations. Instead, pull in InnerBlocks from the wp.editor package (the same used by PlainText), and add the <InnerBlocks /> component to the function’s return statement. Because we’re no longer dealing directly with heading and body attributes, they can be removed from the block’s code entirely, resulting in:

Flipping over to our save.js file, import InnerBlocks just the same way, but take note of the slightly different syntax for the return statement: <InnerBlocks.Content />.

Now in the editor, the block should have its own editing experience embedded within the parent <section> element. Using the heading block and default paragraph block, the same content structure can easily be achieved without fully committing to a format only a developer can alter.

Custom Gutenberg block leveraging the InnerBlocks component

Because of the bare-bones styling, the front-end really isn’t worth showing. Instead, I’d rather move on to the properties used to control how InnerBlocks can be interacted with.


It’s likely that you’ll have some use cases where you need to limit the types of blocks that can be placed inside your InnerBlocks component. This is the purpose of allowedBlocks={ [] }. This prop expects an array of strings, and each string should be the registered name of the block you want to “allow” (e.g. namespace/block”).

If you’re unsure of the block’s name that you’d like to include, you can see all available Gutenberg blocks by executing a command in your browser’s JavaScript console.

  • First, make sure you’re editing a post / page that uses the block editor.
  • Then, open up your browser’s inspect tools (CTRL / CMD + SHIFT + I for Google Chrome).
  • In the console, run this command: 'core/blocks' ).getBlockTypes().

The array of objects returned should each have a “name” property with a string for its value, like: “core/paragraph” or “core/list”. To set the allowed blocks to only paragraph and heading blocks, the syntax would look like:

  • allowedBlocks = [‘core/paragraph’, ‘core/heading’];.

You can also store these values in a variable, if that’s the organization approach you prefer.

template and templateLock

What allowedBlocks doesn’t let you to control, is the block count and arrangement. For that, we’ll have to use the next two props in combination.


The template prop passes a block or collection of blocks for InnerBlocks to render by default. This is useful for blocks that look best with a specific content structure, and in some scenarios, looks better than a completely empty block.

The expected prop value is a bit different from allowBlocks. Instead of an array of strings, template={ [[]] } looks for an array of arrays. Each array item holds a block name (string), it’s initial state (object), and any default child blocks of its own (array). The example on Gutenberg’s official GitHub is a great illustration of all of these options in proper use. Here it is inside the same edit function I’ve been using:

In this example, “core/columns” renders a series of “core/column” blocks. Neither the columns or column blocks have need for default state, so they pass an empty object: {}. Both singular column blocks render their own respective children: an image, and a paragraph. The paragraph has some default state in the form of a input placeholder, and ignores the third array position altogether (as it is not equipped with InnerBlocks). Even simpler, the image block is only defined with its name, rendering the default experience.


The next prop, templateLock, is enforces rules on what the user is allowed to change about your templates. Think of it like a simple form of permissions. There are 3 options to choose from:

  1. all – Disables user control over the template. No moving, deleting, or inserting of other blocks. Use this option if you want to completely lock down your template’s structure.
    • templateLock="all"
  2. insert – Allows the user to change the order of the blocks in your template, but prevents deleting and inserting of new or different blocks. This gives the user a more control, but still forces them to use the blocks you have defined.
    • templateLock="insert"
  3. false – What I haven’t mentioned yet about the first two options, is they also apply to any children using InnerBlocks. Setting templateLock to false, however, prevents this behavior, protecting the child from the parent’s templateLock value.
    • templateLock={ false }


This last template related prop is a quick one. By default, when you place a block using a template, the first child block of that template gains the user’s focus (as opposed to the parent block). Setting this prop to false will disable that, requiring the user to focus on a block within the template manually.

  • templateInsertUpdatesSelection={ false }


This very last property gives you control over the “add new block” experience. You can pass either two predefined components, or your own custom solution. I haven’t found a reason to use this prop yet. For me, the two predefined “appender” components deliver the appropriate experience, and a custom component usually isn’t worth the time investment for my projects. The documentation is pretty light, but the prop expects a function that returns an element or component. Something like:

Final Thoughts

The two most impactful advantages InnerBlocks brings to my workflow are:

  • Enabling complex content structures inside a custom block.
  • Fewer reasons to use php for a block’s front-end over JavaScript.

It may seem trivial to some, but I prefer to use only one language inside a project as much as possible. Developing for WordPress will likely always require some use of php (and there’s nothing wrong with that). I do, however, appreciate building the editor and visitor experiences in JavaScript only. Using php to handle configuration, and JavaScript to deliver the UI creates clear role separation for the two languages, and code that is easier manage and amend.

If you’re looking for more ways to improve your WordPress processes, check out my 5 development tips for better Gutenberg block support.