Create WordPress table of contents automatically – without plugin!

Create WordPress table of contents automatically – without plugin! Thumbnail
Published on 19. May 2020Last updated on 5. June 2020

With many WordPress blog entries or pages, a table of contents can work wonders and give the visitor a quick overview of the structure of the content.


It is simply too cumbersome to create a new table of contents in every post or to update it when changes occur. Installing a plugin (e.g. Easy Table Of Contents) for such a “little thing” is also not a good solution, as it only slows down the site and – as you will see in a moment – you can easily code it yourself.

If you have little or no programming experience, you can still use this table of contents. This is no problem! If there are any questions left, I can also try to help you with the comments.

This is what our end result will look like:

The complete code is programmed by myself and may be used free of charge for any purpose (also commercial) and may be modified as desired!

1. Generate table of contents (PHP script)

The following code generates the table of contents from the headings <h2> and <h3>.


For experienced programmers: This code must be included in the functions.php of the used theme (if used in the child theme).

For inexperienced programmers: In the backend menu (left margin) there is an item “Appearance”. Navigate there to the submenu item “Theme Editor” and then select the “Theme Functions” (functions.php) on the right side under “Theme Files”. There you can scroll to the very end and after the last line of code insert 2-3 empty lines and then insert and save the PHP code below 1:1.

// filter function to generate the table of content (from
function add_table_of_content($content) {

    preg_match_all("/<h[2,3](?:\sid=\"(.*)\")?(?:.*)?>(.*)<\/h[2,3]>/", $content, $matches);
    $tags = $matches[0];
    $ids = $matches[1];
    $names = $matches[2];

    <ul class="table-of-contents">
        <!-- Table of contents by (LH) -->
        <?php for($i = 0; $i < count($names); $i++) { ?>
            <?php if(strpos($tags[$i], "h2") === false || strpos($tags[$i], "class=\"nitoc\"") !== false) continue; ?>
                    <?php if(!empty($ids[$i])) { ?>
                        <a href="#<?php echo $ids[$i]; ?>"><?php echo $names[$i]; ?></a>
                    <?php } else { ?>
                        <?php echo $names[$i]; ?>  
                    <?php } ?>
                    <?php if($i !== count($names) && strpos($tags[$i+1], "h3") !== false) { ?>
                            <?php for($j = 0; $j < count($names) - 1; $j++) { ?>
                                <?php $sub_index = $i + $j; ?>
                                <?php if($j != 0 && strpos($tags[$sub_index], "h2") !== false) break; ?>
                                <?php if(strpos($tags[$sub_index], "h3") === false || strpos($tags[$sub_index], "class=\"nitoc\"") !== false) continue; ?>

                                    <?php if(!empty($ids[$sub_index])) { ?>
                                        <a href="#<?php echo $ids[$sub_index]; ?>"><?php echo $names[$sub_index]; ?></a>
                                    <?php } else { ?>
                                        <?php echo $names[$sub_index]; ?>  
                                    <?php } ?>
                            <?php } ?>
                    <?php } ?>

        <?php } ?>
    return str_replace("<p>{{TABLE_OF_CONTENTS}}</p>", ob_get_clean(), $content);
// add our table of contents filter (from
add_filter('the_content', 'add_table_of_content');

A little tip: If you want to earn some money on the side with your website or blog, this article will tell you everything you need to know about Google Adsense, one of the largest advertising services on the Internet.

2. Pretty up the table of contents (CSS)

Your table of contents is already generated with the previous code. But it would be output as a normal list <ul>. To make the whole thing look a bit nicer, we also add a few lines of CSS.

For experienced programmers: This code must be inserted into a CSS file of the used theme (if used in the child theme). Often this is the top level style.css in the theme folder by default.

For inexperienced programmers: In the same menu item as with the PHP code, you can simply select “Stylesheet” (style.css) on the right. There you also go back to the end and add the following code after the last line and then save the file.

ul.table-of-contents {
  padding: 1rem;
  border-left: 3px solid #cecece;
  background-color: #e6e6e6;
ul.table-of-contents > li:first-child {
    margin-bottom: 0.5rem;
    font-size: 110%;
table-of-contents li {
  list-style: none;

Now the table of contents looks better and is also recognizable as such. Of course, you can adapt everything to your theme and your wishes.

3. Include directory in individual contributions

To display the table of contents on a page, you can simply add {{TABLE_OF_CONTENTS}} as a normal paragraph in the backend editor of the respective post.

Table of contents selector in the Backend view

In addition, you should provide each heading with an HTML anchor. To do this, click on the headline and add an HTML anchor under “Advanced”. This anchor is then attached to the URL with a hash (#) and allows you to jump to the table of contents. You are free to choose the name, but it should match the headline.

Advanced Headline menu

That’s all! If you look at the page now, the table of contents should be output according to the <h2> and <h3> tags.

Exclude menu items

If you want to exclude one or more menu items (like this headline :)), so that they are not displayed in the table of contents, you can additionally give the respective headline the CSS class nitoc. The script will then ignore them.


You did it! With just a few lines of code you can now always generate your own table of contents and don’t have to resort to ready-made plugins anymore. Or even worse: always write and update the tables of contents yourself. Have fun with it! 🙂

Related Posts
Join the Conversation

1 Comment

Your email address will not be published. Required fields are marked *