The Gutenberg WordPress Plugin To Introduce a Table of Contents Block

What was once likely viewed as plugin territory is now a reality as part of the Gutenberg project. Yesterday, the team merged a pull request for a Table of Contents (TOC) block into the plugin’s codebase. It was a contribution driven by developer Zebulan Stanphill, starting nearly a year ago.

The TOC block may feel a bit niche. However, there is obviously a need for it. There are at least two standalone block plugins to handle the feature, and several block library plugins cover it. Last week, a reader asked about such a solution. Soon, the Gutenberg plugin and, eventually, WordPress will have him and others covered.

The block is not currently available in the plugin on It has not officially shipped yet. To test it, users will either need to clone the Gutenberg GitHub repository or grab a ZIP file of the nightly beta. It should land in Gutenberg 10.1 in the coming weeks for those who want to wait.

Including these more-niche blocks is a good direction for the project — a Footnotes block is also a possibility. While it can feel like stepping on the toes of plugin developers, WordPress needs to branch out. There is plenty of room for third-party devs to build other blocks. The experience is degraded when users have to sift through multitudes of plugins to find something core to their writing process. There are limits on what blocks should ultimately be included in the platform. However, WordPress is publishing software. Advanced writing features, such as TOCs and footnotes, belong firmly in the default setup.

The Table of Contents Block

This block is a bit different than other blocks users are accustomed to. A TOC is a list of all the headings in a document. In the case of WordPress and webpages in general, a TOC links to those headings. This allows users to jump around the page. The block depends on other blocks in the content, a slightly new concept for the block editor.

When first adding the block to an empty page, it will display a helper message.

Initial state of the Table of Contents block, displaying a helper message to add Headings with HTML anchors.
Initial TOC block when no Heading blocks are present.

Users must begin adding Heading blocks in their post to make use of the TOC block. Once they are added, each Heading is shown as a list item. The block also properly nests list items for sub-headings — an H3 goes into a sub-list under an H2, for example.

TOC block automatically filling out as new headings are added to the post content in the editor.
Headings become list items in the TOC block.

This is the moment things become more complex. On the web, a TOC needs to link to those headings so that readers can jump to the section they want to view. Right now, this does not happen automatically. Perhaps it will do so in the future, but users must manually add HTML anchors to make the linking part work. Ideally, the initial helper message would link to the documentation page on how to do this for new users.

Adding HTML anchors is easy. However, it could be a ton of work for long posts with dozens of headings.

To add the anchor, users must click on each Heading and navigate to the block options panel. Under the Advanced tab, enter a unique ID. It is easiest to name this after the text itself. A Heading block with “A New World” gets an anchor of a-new-world. This also helps when others are deep linking into posts, creating prettier URLs, such as

Adding anchor links for Heading blocks, which appear as links in the TOC block.
Adding anchors to Heading blocks.

The TOC block does not have any design settings. If users need to change the colors or other design-related elements, it is best to wrap it inside another block, such as Group or Cover.

Grouping the Table of Contents block into a Group block in the WordPress editor.
Wrapping the TOC into a Group block.

If adding a heading for the Group block or before the TOC block, it will be added to the list. It is best to use the Paragraph block as a faux heading and change the font size.

Overall, the block works well. Except for the manual insertion of anchors, it is a welcome addition. Perhaps a plugin author will come along and write the code to make it automatic.


32 responses to “The Gutenberg WordPress Plugin To Introduce a Table of Contents Block”

    • First I’ve heard of it. In fact, my PR was based on another PR which was based on another PR which was based off of a plugin:

      (And it still took almost a year after I created my PR for the block to become polished enough to merge. 😛)

      I never saw the Yoast SEO table of contents block mentioned in or any of the Gutenberg PRs …in fact, I didn’t even know Yoast had a table of contents block.

      • My colleague Ari told you about it here:

        You blew us off, you yourself. You didn’t come up with it so you didn’t want it, that’s how you came across to me.

        You’ve been polishing for a while, ours has been in production for months and is in use by at minimum tens of thousands, probably closer to hundreds of thousands of sites already. What you thought was too complex is the first thing everybody using this is going to ask for, we’ve already built it, we’ve offered to port it. That offer stands, but don’t pretend like we didn’t make it.

        • Oh shoot. I guess I’m even more forgetful than I thought. My apologies.

          But I do stand by my assertion that a block directly modifying the attributes of other blocks is a bad idea. I think that should be implemented at the Heading block. (And speaking of which, if you think you could do that in a PR, please do so. I never wanted to be the only one working on this stuff.)

          And quite frankly, I didn’t think I had any authority over what you did and didn’t do. I’m NOT part of Automattic. I’m not even paid to work on Gutenberg. I’m just some random dude doing this because I genuinely just wanted a good table of contents block in core. If you wanted to create your own Table of Contents block PR, then why would you need my permission?

          Sorry if I came off as dismissing you, but all I did (or at least thought I did) was dismiss an implementation idea that sounded unsound to me. I didn’t think my comment would be interpreted as some prideful command from on high. Good grief.

          If you think there’s something inferior about the current implementation, then just open a PR and I’d be happy to review it and give feedback. (But remember that my feedback is only my own.) Heck, maybe your id-generation code could just be (more-or-less) copy-pasted onto the Heading block, depending on how it works.

          What makes you think I give a darn whether I invented the table of contents block or not? I don’t. I only care about making the implementation perfect, hence why I was hesitant on merging for months because of concerns over hacky PHP code that few other people seemed to care about. If I did care about being “the inventor”, I wouldn’t have based my PR on someone else’s. (And I even created my PR with the intention of it being merged into the other one before the creator of the other PR thought I was better suited to move the development forward.)

          Again, I have nothing against Yoast developing a table of contents block, and I didn’t know my comment would be taken so seriously. I commented only on the technical soundness I perceived from one dude’s comment. I thought updating Heading block ids from the Table of Contents block was a bad idea (and for good reason, considering the undo issues I’d encountered earlier with the block), and so I said I thought it was a bad idea. If anyone disagreed with that, I wish they would have provided a counter-argument. And I wish, if you thought your implementation was better, that you would have just created your own PR.

    • Maybe you’ll have more luck with a Breadcrumbs block. For my own Full Site Editing theme ( Fizzie ) I needed a breadcrumbs block. I started writing my own then realised it would be much easier to call your function if it was available. I’ve just implemented on

  1. Yeah, ideally the Heading block should auto-generate an anchor id when no custom one is set. There’s a few questions surrounding how that should work and how it would be implemented, but I don’t think it will be too difficult. I don’t know if I’ll end up making a PR to implement that or if someone else will step up to the challenge. Here’s the relevant discussion on GitHub:

    • That would be great not just for the TOC. My blog has lots of list posts and crosslinking, and while links to specific anchors on other pages are not usually the best choice, sometimes we want to do it so visitors don’t have to search around the article for what we’re referencing. We stopped bothering with it because settings ids was such a pain.

    • What if the heading block had a checkbox setting for generating an id. It could be a toggle just like how links have toggles for nofollow or open in new tab. then if developers wanted to hook into this function for table of content blocks then it would work :)

      BTW I have no idea how to contribute this to the Github PR.


  2. EHHH…. The Gutenberg Block Library & Toolkit – Editor Plus plugin as default is a must

  3. I think there should be an option to inject TOC automatically or else we will require to literally inject this in every post which is not a practical idea particularly if we think of disabling the previous plugin.

  4. Would be nice if you’d be able to select the depth of headings to be considered. Maybe I’d only want H2’s to be included.

    The entries in the block should be clickable to their respective locations in the article, I can see there’s a technical challenge in this but I’m fully confident that this can be solved.

    • I agree that an option to choose the maximum “depth” would be good. Maybe I’ll open a PR for that later when I have some free time.

      The entries are clickable if the corresponding headings have ids. Ideally, the Heading block should auto-generate ids on insertion. But that has some technical challenges of its own. There’s some discussion about it happening in

  5. I’m generally in favour of adding more blocks to Gutenberg, but i would also like the ability to switch off blocks that aren’t wanted or that have superior plugin implementations.

    By switching off a block, I would expect Gutenberg to also suppress the related CSS and JS payloads. This would be very helpful in the age of Core Web Vitals.

    • I did something similar by allowing the Table of Contents block to be converted to a List block. I did consider allowing the entries to be edited in the ToC block itself, but I figured it would make things too complex and create a confusing UX where some entries would be linked to the text of their respective headings, but others wouldn’t. I figured the explicit convert-to-List button was easier for users to understand and far less complex to implement.

      • That’s true, it can be hard to implement on a user-agnostic level. On my case, it was for a specific client with a very specific need, so handling it that way was easy. Having to accommodate all possible use-cases makes it much harder. The option to convert it to a list is a brilliant idea anyway.

  6. I found that the manages to create id’s for headings automatically which is nice, and toc block boasts a core like block options experience, which also is nice. Both seems to output semantic html and are easy to style. I’m all for core blocks, as it assures some degree of future support, and hopefully will the core block eventually be as flexible and easy to use as the 3rd party options. Otherwise, I find it hard to understand why you’ll want to put it in there?

    • I thought (and other devs seemed to agree) that it would make more sense for the Heading block to generate its own id, rather than put that behavior in the Table of Contents block. (Having the Table of Contents modify sibling blocks feels un-block-like to me. I think block functionality should generally be self-contained or at least only affecting children blocks.) There’s an open issue discussing how to implement that:

      It’s also worth noting that modifying other blocks can be tricky to deal with, since it creates a ton of separate undo actions, which means that trying to undo the action becomes really janky. I just checked, and the Getwid Table of Contents block suffers from this issue. (Earlier versions of the core Table of Contents block suffered from similar issues, which is one of the reasons why it ended up being implemented the way it has been.)

      I notice that the Getwid block allows you to choose which heading levels to display. I don’t think it makes sense to have a separate toggle for each heading level, but I do think a “max heading depth” control would make sense, and I’d like to add it at some point if no one else creates a pull request for it first.

      Concerning other things like changing the list style type (numbers versus bullets) or changing text color, I think a lot of styling stuff like that belongs in the upcoming Global Styles interface, and not as something that can be changed for every individual instance of the block. So I’m a lot more hesitant to add anything like that to the core block knowing how much themes themselves will be changing over the next couple of years.

      It’s also worth noting that the Table of Contents can be converted to a static List block (there’s a button to do so in the toolbar). This allows for a whole lot more customization such as changing the wording of each link, and whatever else you can do with a List block.

      Everything that gets added to core has to be supported for a long time, and I’ve already seen too many cases where something was implemented in a less-than-ideal way, and then changing it later on became twice as hard due to the need for some level of backward compatibility.

      And note that in some cases, it makes more sense for plugins to extend core blocks rather than add entirely new ones. In fact, something like adding ids to all Heading blocks on the page could be implemented in a plugin without even having to modify the core Table of Contents block.

  7. Please, please stop adding features to Gutenberg. There are already far too many block types.

    If I want these “features” I’ll install a plug-in.

    The approach from core should always be about providing a BASIc product, and robust apis to add enhancements if required. Especially as there are so many vital apis missing from core
    eg user taxonomies, custom post status, and object relationships

    • Agree 100%. Keep the default collection simple and only include the items that 80% of users need. Allow others to fill the need for the remaining 20%.

  8. I’ve tried using two TOC blocs from plugins and they were both very good and get improved when problems appear. They are:
    1. The TOC block from Ultimate Addons for Gutenberg,
    2. The TOC block from Kadence Blocks.
    They both have a lot of settings for colours, fonts and sizes.
    I’m currently using the Kadence TOC block after there was a bit of a hiccup with the UAG TOC block. I suspect UAG have made improvements by now so I must take another look at it.
    Both versions automatically insert anchors linked to headings and process editing of headings.
    More than one TOC can be entered in any post. You can set a TOC to work with H1, H2 and H5 say and another TOC to work with H3, H4 and H6 say. (Each heading can be turned on or off in each TOC.) Why would you want to do that? Well one might list sections in a document while the other lists pictures (by giving each picture a heading immediately before it using H4 say.)
    Another question is, “Where to insert a TOC?” Well I put them after the “Introduction”. I start each post with an “Introduction” H2 heading followed by a few paragraphs. The first paragraph has to include the Focus Keyphrase for SEO reasons. This is why the TOC can’t be at the beginning of the post.
    I think anyone wanting to put a TOC in core should look at the facilities and settings offered by TOCs in plugins, such as those I’ve mentioned, to get ideas about what can be done.

  9. Are there really that many people who need this block type to justify adding it to the gutenberg plugin (which of course later gets pushed into WordPress core)? As an add-in block, sure. I don’t see any issue with that, but is it really in such high demand that every WordPress site needs it in the default collection?

    • Adding TOCs is a common feature of writing/publishing software. Therefore, I would argue that it should be pre-packaged with WordPress.

      • since when did WordPress become “writing/publishing software”? Last I checked it was a CMS that “you can use to create a beautiful website, blog, or app”. And I’ll have to respectfully disagree with you on your analysis. I manage a fleet of 300+ WordPress sites, and the number of them that need ToC on a page I can count on one hand. Now, that is anecdotal “evidence” hence my question of: are there that many sites that need this block in default? Given my 10+ years of experience with WordPress, I’d say “no”.

        • WordPress has been writing/publishing software since Day 1. It has evolved into a larger CMS over the years that allows you to do far more, but the process of writing and publishing content has always been central to the platform.

          I could throw out my anecdotal numbers too, but I don’t think that would be conducive to the discussion.


Subscribe Via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

%d bloggers like this: