# Automatic Table of Contents

> Adds a linked table of contents above posts that have enough headings, and gives each heading an anchor so readers can jump straight to a section.

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

## Code

```php
// Minimum number of headings before the table of contents appears.
$wpide_toc_min_headings = 3;
// Heading levels to include (2 = h2, 3 = h3 ...).
$wpide_toc_levels = array( 2, 3 );
// Title shown above the list.
$wpide_toc_title = 'Contents';

add_filter( 'the_content', function ( $content ) use ( $wpide_toc_min_headings, $wpide_toc_levels, $wpide_toc_title ) {
    if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
        return $content;
    }

    $levels  = implode( '', array_map( 'absint', $wpide_toc_levels ) );
    $pattern = '/<h([' . $levels . '])([^>]*)>(.*?)<\/h\1>/is';

    if ( ! preg_match_all( $pattern, $content, $found ) || count( $found[0] ) < $wpide_toc_min_headings ) {
        return $content;
    }

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

    $result = preg_replace_callback( $pattern, function ( $m ) use ( &$used, &$items ) {
        $text  = trim( wp_strip_all_tags( $m[3] ) );
        $attrs = $m[2];

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

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

            $attrs .= ' id="' . esc_attr( $id ) . '"';
        }

        $used[ $id ] = true;
        $items[]     = array( 'level' => (int) $m[1], 'id' => $id, 'text' => $text );

        return '<h' . $m[1] . $attrs . '>' . $m[3] . '</h' . $m[1] . '>';
    }, $content );

    if ( null === $result || ! $items ) {
        return $content;
    }

    $top  = min( wp_list_pluck( $items, 'level' ) );
    $html = '<nav class="wpide-toc" aria-label="' . esc_attr( $wpide_toc_title ) . '" style="margin:0 0 2em;padding:1em 1.25em;border:1px solid rgba(0,0,0,.12);border-radius:6px;">';
    $html .= '<p style="margin:0 0 .5em;"><strong>' . esc_html( $wpide_toc_title ) . '</strong></p><ul style="margin:0;">';

    foreach ( $items as $item ) {
        $indent = $item['level'] > $top ? ' style="margin-left:' . ( ( $item['level'] - $top ) * 1.25 ) . 'em;"' : '';
        $html  .= '<li' . $indent . '><a href="#' . esc_attr( $item['id'] ) . '">' . esc_html( $item['text'] ) . '</a></li>';
    }

    return $html . '</ul></nav>' . $result;
}, 20 );
```

## Notes

Change the minimum heading count, the heading levels or the title in the variables at the top.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Automatic Table of Contents" 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/
