Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Toc Laravel Package

caseyamcl/toc

caseyamcl/toc is a PHP library that generates a nested table of contents from HTML headings. It parses h1–h6 elements, adds anchor IDs, and outputs a customizable, hierarchical TOC list for documentation and long-form pages.

Deep Wiki
Context7

Getting Started

composer require caseyamcl/toc

Start with the README and then src/TocGenerator.php — that class exposes the core API. TocItem (and any renderer classes) is where to look when you need to customize markup.

The minimal day-to-day flow is: parse an HTML fragment, inject anchors, render a nested list.

use Caseyamcl\Toc\TocGenerator;

$html = '<h1>Guide</h1><h2>Install</h2><h2>Usage</h2><h3>Options</h3>';

$generator = new TocGenerator();

// Add unique IDs to headings so TOC links resolve
$anchored = $generator->addAnchors($html);

// Render a nested <ul> TOC
echo $generator->getHtmlToc($anchored);

// Or work with structured items
foreach ($generator->getToc($anchored) as $item) {
    printf("%d | %s | %s\n", $item->getLevel(), $item->getTitle(), $item->getUrl());
}

First use case: a docs or blog sidebar. Generate the TOC when content is published, store the resulting HTML and anchored body, and render both from the view layer.

Implementation Patterns

Generate at write time, not read time. Put TOC generation in a model observer, form request handler, or a small action class. Store toc_html and the anchored body_html columns and render them directly.

final class PrepareArticleContent
{
    public function __construct(private TocGenerator $generator) {}

    public function __invoke(string $body): array
    {
        $anchored = $this->generator->addAnchors($body);

        return [
            'body_html' => $anchored,
            'toc_html'  => $this->generator->getHtmlToc($anchored),
        ];
    }
}

Bind the generator as a singleton. It is stateless and cheap to reuse. In Laravel:

$this->app->singleton(TocGenerator::class, fn () => new TocGenerator());

Customize markup by iterating items instead of using getHtmlToc(). This is the cleanest way to add Bootstrap/Tailwind classes, <nav aria-label="Table of contents">, or link icons.

function renderToc(iterable $items): string
{
    $out = '<ul class="toc">';
    foreach ($items as $item) {
        $out .= sprintf(
            '<li><a href="%s">%s</a>',
            e($item->getUrl()),
            e($item->getTitle())
        );

        if ($children = $item->getChildren()) {
            $out .= renderToc($children);
        }

        $out .= '</li>';
    }

    return $out . '</ul>';
}

Pick levels per content type. Blog posts usually want h2–h3 only; long-form docs want h1–h6. Pass min/max levels to the generator rather than filtering afterward, so the nesting is correct from the start.

Use the same ID strategy on both sides. If you generate a TOC from HTML A and inject anchors into HTML B, links will drift. Always anchor first, then build the TOC from the anchored result.

Cache aggressively. Key the cache by the content hash or updated_at. There is no reason to parse an article on every request.

Fragments first. Feed the content region into the generator, not the full page. Otherwise site headers, footers, and widget headings leak into the TOC.

Gotchas and Tips

DOMDocument wrapper noise. Parsing fragments can add <html> and <body> wrappers depending on how the input is loaded. If the output must remain a bare fragment, extract the body children or use the appropriate libxml flags. Verify that a round-trip does not change the surrounding markup.

UTF-8 handling. DOMDocument can mangle multibyte headings unless the encoding is signaled. Test with accented and non-Latin headings early; a common fix is prefixing the string with <?xml encoding="UTF-8"> or converting explicitly before parsing.

Duplicate anchors. Two headings titled "Overview" can produce colliding or unpredictable IDs. Add your own prefix or deduplicate heading IDs before generating the TOC, and make sure the same IDs are present in the rendered body. A broken TOC usually means an ID mismatch, not a parser bug.

Skipped heading levels. An h2 followed directly by an h4 produces awkward nesting. Decide whether to normalize source headings, pad intermediate levels, or accept the gap. Check CSS depth assumptions for ul ul ul.

Call order matters. addAnchors() then getHtmlToc() is the reliable sequence. Calling the TOC generator first gives you links without matching IDs unless you re-process the body.

Min/max levels do not remove anchors from excluded headings in every configuration. If you only want IDs on TOC-visible headings, post-process or restrict the input.

It is not a sanitizer. The library parses HTML; it does not make untrusted HTML safe. Sanitize user content before storage and rendering. Anchor injection is a transformation, not a security boundary.

Performance. Parsing is linear in document size, and DOM operations are not free. Batch-generating TOCs for an index page of hundreds of articles is a common accidental slowdown — cache or precompute.

Testing strategy. Assert three things: the number of TOC entries, the nesting depth, and that every href in the TOC has a matching id in the anchored body. That last check catches nearly every real-world regression.

Extension points. Subclass TocGenerator or build a renderer around the item collection rather than post-processing the library's HTML string. Check the constructor options and renderer classes before writing custom string manipulation — there is often a supported hook.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package