# Automatic Table of Contents for Long Posts

> Builds a clickable table of contents from the H2 and H3 headings of long posts and places it above the content, adding anchor IDs to the headings.

- Type: PHP
- Runs: Frontend only
- Category: [Frontend & Design](https://wpide.com/library/category/frontend/)
- Tags: table-of-contents, headings, navigation, content
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/frontend-table-of-contents/

## Code

```php
add_filter( 'the_content', function ( $content ) {
    $min_headings = 3;            // Show the table only with this many headings or more.
    $post_types   = array( 'post' );
    $title        = 'Contents';

    if ( ! is_singular( $post_types ) || ! in_the_loop() || ! is_main_query() ) {
        return $content;
    }

    if ( ! preg_match_all( '#<h([23])([^>]*)>(.*?)</h\1>#is', $content, $matches, PREG_SET_ORDER ) || count( $matches ) < $min_headings ) {
        return $content;
    }

    $items = '';
    $used  = array();

    foreach ( $matches as $match ) {
        $text = wp_strip_all_tags( $match[3] );

        if ( preg_match( '#\sid=["\']([^"\']+)["\']#', $match[2], $id_match ) ) {
            $id = $id_match[1];
        } else {
            $id   = sanitize_title( $text );
            $id   = $id ? $id : 'section';
            $base = $id;
            $n    = 2;

            while ( isset( $used[ $id ] ) ) {
                $id = $base . '-' . $n++;
            }

            $content = preg_replace( '#' . preg_quote( $match[0], '#' ) . '#', '<h' . $match[1] . $match[2] . ' id="' . esc_attr( $id ) . '">' . $match[3] . '</h' . $match[1] . '>', $content, 1 );
        }

        $used[ $id ] = true;
        $items      .= sprintf( '<li class="wpide-toc-h%1$s"><a href="#%2$s">%3$s</a></li>', esc_attr( $match[1] ), esc_attr( $id ), esc_html( $text ) );
    }

    $toc = '<nav class="wpide-toc" aria-label="' . esc_attr( $title ) . '" style="margin:0 0 2em;padding:16px 20px;border:1px solid rgba(127,127,127,.3);border-radius:6px;">'
        . '<p style="margin:0 0 .5em;font-weight:600;">' . esc_html( $title ) . '</p><ul style="margin:0;padding-left:1.2em;">' . $items . '</ul></nav>'
        . '<style>.wpide-toc-h3{margin-left:1em;list-style:circle;}</style>';

    return $toc . $content;
}, 20 );
```

## Notes

Appears on posts with at least $min_headings headings. Change $post_types to use it on pages too.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Automatic Table of Contents for Long Posts" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
