markdown-toc
Generates and injects a markdown table of contents into README files, using Remarkable to parse headings.
Repository Health
Technical Analysis
markdown-toc is a Node.js library and CLI for generating a table of contents from markdown headings. It parses a markdown file with Remarkable, extracts every heading, and renders a nested bullet list of links, handling repeated heading text, YAML front matter, and headings that appear inside fenced code blocks without mistaking them for real sections.
Beyond the one-line toc() API, it exposes a Remarkable plugin (toc.plugin), a JSON heading tree (toc.json), and an insert() helper that finds <!-- toc --> / <!-- tocstop --> markers in an existing file and rewrites just that block, making it a common build-script dependency for keeping README tables of contents in sync across thousands of open source projects.
What You Get
- Programmatic API -
require('markdown-toc')returns a function that parses a markdown string and returns{content, json, highest, tokens}. - In-place TOC injection -
toc.insert()finds<!-- toc -->/<!-- tocstop -->markers in a file and rewrites just that block, safe to re-run repeatedly. - CLI binary - the bundled
markdown-toccommand supports-i(edit file in place),--json,--append,--bullets,--maxdepth, and--indentflags for build-script use. - Remarkable plugin mode -
toc.plugin(options)registers as a Remarkable renderer plugin so TOC generation can be part of a larger markdown rendering pipeline. - Customizable slugify/filter/titleize hooks - override how heading text becomes a link, filter out unwanted headings, or reformat heading text for the TOC entry.
Common Use Cases
- Keeping README TOCs current - a
pretest/prepublishnpm script runsmarkdown-toc -i README.mdso the table of contents never drifts from the actual headings. - Generating docs site navigation - static site generators call the programmatic API to build a heading-based nav sidebar from each markdown source file.
- Filtering noisy headings - projects whose markdown includes fenced-code examples containing
#comments rely on markdown-toc’s code-block-aware parsing to avoid false TOC entries. - Custom TOC rendering - tools consume
toc.json’s heading token array directly to build a non-bullet-list TOC, such as a dropdown or a JSON sitemap.
Under The Hood
Architecture
The package is a thin single-purpose module built around index.js, which overrides a Remarkable renderer’s render method to walk the token stream, collect heading_open tokens, and assemble a JSON heading tree before rendering it back out as a bullet list; lib/insert.js is a separate, front-matter-aware module handling the <!-- toc -->/<!-- tocstop --> marker-splitting logic for in-place file edits, and cli.js is a thin binary wrapper composing index.js and lib/insert.js with minimist-parsed flags. There is no dependency injection or layering beyond this three-module split; data flows one way from a markdown string through the Remarkable override into a token array and finally a bullet string, and the small, isolated surface (toc(), toc.plugin, toc.insert, toc.json) means the renderer-override internals could change with limited blast radius on consumers.
Tech Stack
Plain CommonJS Node.js with no build step, no TypeScript, and an engines field pinned to >=0.10.0, reflecting the package’s age. It depends on an old fork of the CommonMark parser (remarkable@^1.7.1) plus a set of small single-purpose utility packages (gray-matter for front matter, list-item for bullet rendering, minimist for CLI parsing, mixin-deep, object.pick, lazy-cache, diacritics-map, markdown-link, repeat-string, strip-color, concat-stream). Dev tooling is limited to mocha for tests and gulp-format-md/verb for generating the README from a template; CI is configured only via a legacy AppVeyor Windows build, with no modern GitHub Actions workflow.
Code Quality
The test suite (test/test.js) is fixture-driven, comparing generated TOC output against a test/expected/ directory across many edge cases (front matter, repeated headings, code blocks, custom bullets), which gives reasonable regression coverage for a package this size. There is no type system (plain pre-ES6-style JS using var), and error handling is minimal: the only explicit throw guards against multiple TOC markers in one file, with no other input validation or try/catch around parsing. An .eslintrc.json exists but there’s no CI job enforcing it.
API Design
The public API is small and consistent: toc(str, options) for a one-line call, toc.plugin/toc.json/toc.insert for the same operation in different contexts, and utility exports (toc.slugify, toc.linkify, toc.bullets) for consumers who want to build a fully custom renderer. Options (filter, slugify, bullets, maxdepth, firsth1, stripHeadingTags, append) are documented with examples and mirrored 1:1 by CLI flags, so getting started requires close to zero boilerplate — require('markdown-toc')('# H1').content is the entire integration for the common case.