laravel-auto-toc maintained by prashant-malla
Description
Automatically generate a Table of Contents from rich text editor HTML content in Laravel.
Author
Last update
2026/02/28 17:03
(dev-main)
License
Downloads
11
Laravel Auto TOC
Automatically generate a Table of Contents (TOC) from rich text editor HTML content in Laravel.
The package parses HTML headings (h2–h6 by default), injects unique id attributes, and stores a structured TOC via a polymorphic relationship — perfect for blogs, tours, documentation, and any CKEditor / TinyMCE / Trix content.
Installation
composer require prashant-malla/laravel-auto-toc
The service provider is auto-discovered. If you need to register it manually:
// config/app.php → providers
PrashantMalla\AutoToc\AutoTocServiceProvider::class,
Publish & run migrations
php artisan vendor:publish --tag=auto-toc-migrations
php artisan migrate
Publish config (optional)
php artisan vendor:publish --tag=auto-toc-config
Usage
1. Use the trait on any Eloquent model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use PrashantMalla\AutoToc\Traits\HasTableOfContent;
class Blog extends Model
{
use HasTableOfContent;
/**
* The HTML field that contains the rich text content.
*/
protected static string $tocSourceField = 'content';
}
That's it! Every time a Blog is saved the package will:
- Parse the
contentfield for headings. - Inject unique
idattributes into each heading tag. - Update the
contentfield with the modified HTML. - Store / update a polymorphic
TableOfContentrecord with the structured TOC.
2. Access the TOC
$blog = Blog::with('toc')->find(1);
// Structured array of headings
$toc = $blog->toc->content;
// Each entry looks like:
// [
// 'title' => 'Getting Started',
// 'anchor' => 'getting-started',
// 'level' => 2,
// ]
3. Render the TOC in a Blade view
Flat list (simple)
@if($blog->toc && count($blog->toc->content))
<nav class="toc">
<h2>Table of Contents</h2>
<ul>
@foreach($blog->toc->content as $item)
<li class="toc-level-{{ $item['level'] }}">
<a href="#{{ $item['anchor'] }}">{{ $item['title'] }}</a>
</li>
@endforeach
</ul>
</nav>
@endif
Style the indentation with CSS using the level classes:
.toc-level-2 { margin-left: 0; }
.toc-level-3 { margin-left: 1rem; }
.toc-level-4 { margin-left: 2rem; }
.toc-level-5 { margin-left: 3rem; }
.toc-level-6 { margin-left: 4rem; }
Nested list (hierarchical)
For a properly nested <ul> structure that mirrors the heading hierarchy:
@if($blog->toc && count($blog->toc->content))
<nav class="toc">
<h2>Table of Contents</h2>
@php $prevLevel = 0; @endphp
@foreach($blog->toc->content as $item)
@if($item['level'] > $prevLevel)
{{-- Open new nested <ul> for each level deeper --}}
@for($i = 0; $i < $item['level'] - $prevLevel; $i++)
<ul>
@endfor
@elseif($item['level'] < $prevLevel)
{{-- Close </li> and </ul> for each level back up --}}
@for($i = 0; $i < $prevLevel - $item['level']; $i++)
</li></ul>
@endfor
</li>
@else
</li>
@endif
<li><a href="#{{ $item['anchor'] }}">{{ $item['title'] }}</a>
@php $prevLevel = $item['level']; @endphp
@endforeach
{{-- Close any remaining open tags --}}
@for($i = 0; $i < $prevLevel - ($blog->toc->content[0]['level'] ?? 0); $i++)
</li></ul>
@endfor
</li></ul>
</nav>
@endif
Example output
Given this HTML content:
<h2>Getting Started</h2>
<p>Introduction text...</p>
<h3>Prerequisites</h3>
<p>You need PHP 8.1...</p>
<h3>Installation</h3>
<p>Run composer...</p>
<h2>Configuration</h2>
<p>Config details...</p>
<h3>Environment</h3>
<p>Set your .env...</p>
The stored TOC array will be:
[
['title' => 'Getting Started', 'anchor' => 'getting-started', 'level' => 2],
['title' => 'Prerequisites', 'anchor' => 'prerequisites', 'level' => 3],
['title' => 'Installation', 'anchor' => 'installation', 'level' => 3],
['title' => 'Configuration', 'anchor' => 'configuration', 'level' => 2],
['title' => 'Environment', 'anchor' => 'environment', 'level' => 3],
]
And the nested Blade template renders:
<nav class="toc">
<h2>Table of Contents</h2>
<ul>
<li><a href="#getting-started">Getting Started</a>
<ul>
<li><a href="#prerequisites">Prerequisites</a></li>
<li><a href="#installation">Installation</a></li>
</ul>
</li>
<li><a href="#configuration">Configuration</a>
<ul>
<li><a href="#environment">Environment</a></li>
</ul>
</li>
</ul>
</nav>
Customising Heading Levels
Per-model
class Blog extends Model
{
use HasTableOfContent;
protected static string $tocSourceField = 'content';
/** Only extract h2 and h3 headings */
protected static array $tocHeadingLevels = ['h2', 'h3'];
}
Globally via config
// config/auto-toc.php
return [
'heading_levels' => ['h2', 'h3', 'h4', 'h5', 'h6'],
];
License
The MIT License (MIT). Please see LICENSE for more information.