One of the most versatile components that Gutenberg block developers have is the <InnerBlocks/>
component. This component allows other blocks to be inserted into it. WordPress Core ships with the Group and Columns blocks that both use InnerBlocks internally to allow the creation of very complex block structures.
The InnerBlocks component solves some interesting problems for custom blocks, for example creating a list of ordered items. By leveraging InnerBlocks, one can skip having to build a bespoke system and rely on Gutenberg to handle the insertion, deletion, and ordering of items. It is very flexible and easy to use. However, while working with it I discovered that there is no built-in way to limit the number of blocks that can be inserted.
The renderAppender prop will allow us to solve this issue
RenderAppender Overview
Before we talk about the solution, let briefly discuss the renderAppender prop and how it works.
Released with WordPress. 5.3, the renderAppender
prop allows us to control the interface for inserting blocks into the related <InnerBlocks/>
component.
There are two predefined appenders available in WordPress core:
- <InnerBlocks.DefaultBlockAppender />
- <InnerBlocks.ButtonBlockAppender />
<InnerBlocks.DefaultBlockAppender />
As the name indicates, this is the default appender. If the renderAppender
prop is undefined
, then this is the appender that is used. It automatically inserts whichever block is configured as the default block via wp.blocks.setDefaultBlockName
– which by default is the Paragraph block.
If you’ve worked in the block editor at all you’ve already seen this.

So when working with <InnerBlocks/>
, you don’t need to pass anything to renderAppender
to get this style of appender. If fact, you can leave it off entirely.
<InnerBlocks />
<InnerBlocks.ButtonBlockAppender />
This appender is displayed as a + ( plus ) icon button that, when clicked, displays the block inserter. This appender does not insert a default block.
This appender is seen in the Group or Columns block.

To use this in your <InnerBlocks
/>, you have to pass a function to renderAppender
that returns the <InnerBlocks.ButtonBlockAppender />
component.
<InnerBlocks
renderAppender={ () => (
<InnerBlocks.ButtonBlockAppender />
) }
/>
No appender at all
The last option is to pass false
to renderAppender
to not display any appender at all.
<InnerBlocks
renderAppender={false}
/>
Solution
Now that we have an understanding of how the renderAppender
prop works, let us use it to solve the problems of limiting the number of blocks allowed.
After some experimentation, the actual solution is fairly straight forward, we just need a way to count how many blocks have been inserted and if it’s more than we want, we set renderAppender
to false
so that nothing is displayed and the user cannot insert any more blocks until one is removed.
The core/block-editor
datastore provides a getBlock()
selector that we can use to get the information we need. We pass it the clientId
of our block and we can get the list of innerBlocks
, count them, and either display the appender or return false.
Below is the code that you would place into the edit
property of a block, it’s very simple and but demonstrates the the solution.
import { useSelect } from '@wordpress/data';
import { InnerBlocks } from '@wordpress/block-editor';
const InnerBlockWithLimit = ( props ) => {
const { clientId } = props;
const innerBlockCount = useSelect( ( select ) => select( 'core/block-editor' ).getBlock( clientId ).innerBlocks );
const appenderToUse = () => {
if ( innerBlockCount.length < 10 ) {
return (
<InnerBlocks.ButtonBlockAppender/>
);
} else {
return false;
}
}
return (
<InnerBlocks
renderAppender={ () => appenderToUse() }
/>
)
}
Have you seen any other issues when working with <InnerBlocks/>
or want to discuss this approach? If so, leave a comment below!
Thanks a lot, this has helped me better understand the renderAppender functionality of InnerBlocks. I have one strange behaviour in my custom block, which I would like to solve: I need a switch between a false state (for an initial block setup) and a visible renderAppender when the content of the block has loaded (to add further child blocks on demand). This would perfectly fit the solution you described. But as soon as I use the “DefaultBlockAppender” the small appender button is always visible, left aligned and about 80 px down from my last child block. Whereas when I completely omit the renderAppender prop in I get the normal behaviour of a centered appender button that is hidden by default and only visible when the cursor is next to the button. Using the “ButtonBlockAppender” works fine. Not sure if this is the intended behaviour or a small bug.
Hi Jim! Thanks for your comment!
If I am understanding your issue, that seems like expected behaviour. If you leave off the renderAppender prop, you will get the default one. That is why we have to explicitly set it to false in the example code.
Thanks very much for the post here. It was extremely useful in being able to create my own version of the Columns block (which includes far too much functionality for my liking!).
I don’t use a build step and found content difficult to use in my case. This post however set the imaginary lightbulb off and simplified the approach I was pursuing.
I’m glad it helped, thanks for reaching out!
Hi, thanks for the useful solution.
There’s one issue with that: I can still insert as many child blocks as I want via the block inserter in the top bar or by hovering between items and clicking the element classified as .block-editor-block-list__insertion-point
Do you have an idea how to disable it as well?
I found this helpful post while looking for a way to count how many blocks were inside another. Got that sorted with a bonus of knowing how to limit blocks too!
Thanks!