# gh-md-toc: generating a GitHub README table of contents without installing anything

> gh-md-toc is a single shell script that turns a README, a wiki page or stdin into a Markdown table of contents. It is convenient, it is MIT licensed, and it is not the right tool for Windows or for large batch jobs.

**ekalinin/github-markdown-toc** — Easy TOC creation for GitHub README.md

- Repository: https://github.com/ekalinin/github-markdown-toc
- Website: https://ekalinin.github.io/github-markdown-toc/
- Stars: 3,301 · Forks: 2,675
- Language: Shell
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/ekalinin-github-markdown-toc

## The gap gh-md-toc fills in GitHub's Markdown rendering

GitHub renders Markdown headings as anchors, but it does not build a table of contents for you. The README links to isaacs/github issue 215 as the problem it tries to fix, and the project describes itself as being for people who want a TOC "without installing additional software". That last phrase is the whole design constraint. gh-md-toc is a single shell script named gh-md-toc in the repository root, not a package you install through a language runtime. You download the file, mark it executable, and run it.

The audience is narrow and clear. It is someone editing a README.md or a GitHub wiki page who wants a nested list of links at the top, with anchors that actually resolve on github.com. That is a different job from writing documentation in a static site generator, where the TOC is a template concern. Here the output is text you paste back into the same file you read it from.

## How the script turns headings into a nested link list

The script accepts three kinds of input: stdin, local files, and remote files. The README is explicit that remote input means "html files on github.com", not raw Markdown fetched over HTTP. That distinction matters. For a remote README or wiki page, gh-md-toc reads the rendered GitHub HTML and derives the heading structure and anchors from it. For a local file or stdin, it parses the Markdown source directly.

The two paths explain the release history. Versions 0.9.1 and 0.10.0 both carry the note "Update for a new GH HTML output", which is what you would expect from a tool whose remote mode depends on GitHub's page markup. Local mode is insulated from that; remote mode is not. The output is a nested bullet list, and indentation reflects heading depth, as the README's own table of contents shows with sub-items for Usage, STDIN, Local files and so on.

Multiple files are supported in one invocation. In that case the README's example shows each file becoming a top-level entry, with the file's own headings nested beneath it, and the links pointing at the file path plus an anchor rather than at the current document.

## Installing gh-md-toc and generating a first table of contents

There is no package manager step on Linux or macOS. The README gives a manual installation that downloads the script and marks it executable. On Linux it uses wget:

```bash
$ wget https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc
$ chmod a+x gh-md-toc
```

On macOS the same thing with curl, writing to a file:

```bash
$ curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc
$ chmod a+x gh-md-toc
```

If you use Basher, the README documents a one-line install and notes that gh-md-toc then lands on your PATH automatically:

```bash
$ basher install ekalinin/github-markdown-toc
```

Once the script is executable, the first real use is to point it at a local README. The README's example runs it on a file path and prints a table of contents with a "Table of Contents" heading, an equals-sign underline, and nested bullets:

```bash
➥ ./gh-md-toc ~/projects/Dockerfile.vim/README.md
```

For a file that lives on github.com, you pass the blob URL instead, and the README shows the wiki form working the same way:

```bash
➥ ./gh-md-toc https://github.com/ekalinin/nodeenv/wiki/Who-Uses-Nodeenv
```

If you would rather not copy from the terminal, the README documents redirecting stdout to a file, for example appending `> table-of-contents.md` to the command, which writes the result into that file in the current directory. Stdin is also supported: piping a README into `./gh-md-toc -` produces the same list without a surrounding "Table of Contents" block.

## The Docker path and the Makefile targets

The repository ships a Dockerfile, so you can run the script without keeping the file on your machine. It is based on debian, installs curl, copies gh-md-toc into /app, marks it executable, and sets the entrypoint to the script with an empty CMD.

```dockerfile
FROM debian

RUN apt update -y && \
  apt upgrade -y && \
  apt install curl -y

WORKDIR app

COPY gh-md-toc .

RUN chmod +x gh-md-toc

ENTRYPOINT ["./gh-md-toc"]
CMD []
```

Because the entrypoint is the script itself, arguments you pass to the container are passed straight through to gh-md-toc. The README's Docker section is split into Local and Public subsections, which is the usual split between building the image yourself and pulling a published one.

The Makefile is small and worth reading before you contribute. Its release target depends on test, tags a version extracted from the script itself with a grep pattern, and pushes tags to origin master. The test target runs `bats tests`, and lint runs `shellcheck -e SC2008 gh-md-toc`. So the project's own quality gate is two tools, bats and shellcheck, plus a CI workflow under .github.

## Where gh-md-toc is the wrong choice

Windows is the first clear boundary, and the project states it itself rather than leaving you to discover it. The README says that if you want it on Windows you are better off with the golang implementation, github-markdown-toc.go, which it describes as more solid, reliable, and capable of parallel processing, and without dependencies. That is an unusually direct recommendation against the shell script, and it should be taken at face value.

The second limitation is the copy-and-paste workflow. For a single local file the tool prints a list; it does not rewrite your README in place. The README's own instruction is "copy/paste result from console into original README.md", with redirection to a separate file offered as the alternative. There is an auto insert and update section in the README's table of contents, so that path exists, but the manual flow is the documented default and it is what most examples show.

The third is remote-mode fragility. Since remote input is GitHub's rendered HTML, a change in that markup can change or break anchor extraction. Two consecutive releases were dedicated to exactly that. If your TOC is generated in CI from a remote URL, that dependency is on GitHub's front end, not on a stable API. Local mode avoids it, at the cost of running the script where the file is.

## github-markdown-toc.go and the Python and editor alternatives

The closest alternative named by the project is github-markdown-toc.go, by the same author. The difference is not cosmetic: it is a compiled Go binary rather than a shell script, so it has no runtime dependencies and can process work in parallel. If you are generating TOCs for many files, or you are on Windows, that is the version to look at. gh-md-toc stays attractive where you cannot or will not install a binary and a curl-and-chmod is the whole setup.

Editors cover part of the same ground differently. A Markdown editor or an nvim plugin that builds a TOC works inside the buffer you are already editing, which removes the paste step entirely, but the anchor rules it uses are its own. The reason to prefer gh-md-toc for a GitHub README is that its remote mode reads GitHub's actual rendered output, which is the same source of truth your readers click on. That is a real difference in approach, not a preference.

Cloud-hosted Markdown also matters here. GitLab and Azure DevOps both render Markdown and both have their own anchor conventions, and gh-md-toc is built around github.com. The README's remote examples are all github.com blob and wiki URLs. Using it against another host's rendered HTML is not something the documentation covers.

## Maintenance, licence and what an upgrade actually costs

The repository is not archived, and the last push was on 2026-09-27, so it is being touched. The release cadence is slow and deliberate: 0.9.0 added the --skip-header and --indent options in November 2023, 0.9.1 followed the same day to track a GitHub HTML change, and 0.10.0 arrived in March 2024 with the same note. There is no 1.0 and no stated compatibility policy.

That history is the upgrade cost in practice. The script has no version flag documented in the README, and the Makefile derives the release tag by grepping a version string out of the script itself, so the version lives in the file. If you vendor gh-md-toc into a repository or a Docker image, you are pinning a single script, and the thing that will force you to update it is a change in GitHub's rendered HTML affecting remote mode. Local-mode users have far less reason to chase releases.

The licence is MIT, stated in the repository and in the LICENSE file. That is permissive and short, but this is not legal advice; read the LICENSE file and your own organisation's policy before redistributing the script inside a product.

## Conclusion

Adopt gh-md-toc if you maintain a README or a GitHub wiki page and want a TOC you can paste back by hand, or if you want to run the same script inside a small Docker image or a GitHub Actions step. Skip it if you work on Windows, where the README points at github-markdown-toc.go, or if you need to regenerate dozens of files in one pass, since each invocation re-fetches remote input. Before relying on it, run ./gh-md-toc on one of your own headings and confirm the generated anchors match what GitHub renders, because the release notes for 0.9.1 and 0.10.0 are both titled "Update for a new GH HTML output", which tells you anchor behaviour tracks GitHub's renderer rather than the Markdown spec.

## FAQ

### How do I generate a table of contents for a GitHub README with gh-md-toc?

Download the gh-md-toc script, run chmod a+x on it, then pass a local path or a github.com blob URL as the argument. The script prints a nested bullet list of links that you paste back into the README, or redirect to a file.

### Can gh-md-toc generate a Markdown TOC automatically in CI?

The README has a section on TOC generation with GitHub Actions, and the repository contains a CI workflow. The documented default workflow is still running the script and pasting or redirecting the result, and there is an auto insert and update section in the README's table of contents.

### Does gh-md-toc work on Windows?

The README says that if you want it on Windows you are better off using the golang implementation, github-markdown-toc.go, which it describes as more solid, reliable, and capable of parallel processing. The script itself is documented as tested on Ubuntu and macOS High Sierra.

### Does gh-md-toc modify my README file in place?

No. The README's instruction is to copy and paste the console result into the original README.md, or to redirect the output to a separate file such as table-of-contents.md. The README also lists an auto insert and update section.

### What does gh-md-toc use to build anchors for remote files?

The README states that remote files are html files on github.com, so the script reads GitHub's rendered HTML rather than raw Markdown. Releases 0.9.1 and 0.10.0 are both described as updates for a new GH HTML output.

## Sources

- [ekalinin/github-markdown-toc on GitHub](https://github.com/ekalinin/github-markdown-toc)
- [License: MIT](https://github.com/ekalinin/github-markdown-toc/blob/master/LICENSE)
- [Project website](https://ekalinin.github.io/github-markdown-toc/)
- [README](https://github.com/ekalinin/github-markdown-toc/blob/master/README.md)
- [Releases](https://github.com/ekalinin/github-markdown-toc/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ekalinin-github-markdown-toc
