Skip to main content

mdlinkcheck

CI License: MIT Python 3.9+

Find broken relative links and #anchors in Markdown files: offline, fast, with no dependencies, and using the same heading-anchor rules as GitHub.

$ mdlinkcheck src/engine
src/engine/prague.md:72:38: #BlobsBundleV1: no heading or anchor '#BlobsBundleV1' in this file [missing-anchor]
src/engine/refactor-ssz.md:23:23: #max-constants: no heading or anchor '#max-constants' in this file; did you mean '#max_-constants'? [missing-anchor]
src/engine/refactor-ssz.md:524:26: #get-forkpayloadspayloadid: no heading or anchor '#get-forkpayloadspayloadid' in this file; did you mean '#get-payloadspayloadid'? [missing-anchor]
src/engine/refactor.md:447:39: ./refactor-ssz.md#max-constants: no heading or anchor '#max-constants' in 'refactor-ssz.md'; did you mean '#max_-constants'? [missing-anchor]
mdlinkcheck: 13 files, 460 links, 4 broken

Why

External-URL checkers (lychee, markdown-link-check) are great at telling you a website is down. They are much less good at the links that break inside a repository:

  • a section gets renamed and every other-doc.md#old-title link silently stops scrolling anywhere;
  • a heading contains code, punctuation or underscores, so its real anchor isn't what you'd guess (## `MAX_*` constants is #max_-constants, ## Definition of `channel_id` is #definition-of-channel_id);
  • a file moves and ../dump.md becomes ./dump.md in one place but not another;
  • a link works on your Mac but not on GitHub, because the file name differs only in case.

GitHub doesn't warn about any of these. mdlinkcheck does, without touching the network, so it is fast enough for a pre-commit hook and deterministic enough for CI.

Install

pipx install git+https://github.com/mikeua/mdlinkcheck
# or
pip install git+https://github.com/mikeua/mdlinkcheck

Python 3.9 or newer; no third-party dependencies.

Usage

mdlinkcheck                 # every Markdown file in the repository
mdlinkcheck docs README.md  # specific files and directories

It exits with 1 if any link is broken, 0 otherwise, and 2 on usage errors.

Option Description
-e, --exclude GLOB Skip files matching GLOB (repeatable), e.g. -e 'vendor/*' -e CHANGELOG.md.
-i, --ignore REGEX Don't check link targets matching REGEX (repeatable), e.g. -i '^generated/'.
-s, --site-root DIR Pages directory of a docs site (Docusaurus, VitePress, Vocs, ...). /route links resolve against it, and links inside it may leave out .md. Repeatable, e.g. for a public/ assets folder.
--no-absolute Don't check links that start with /.
--root DIR Directory that / links are relative to (default: the git top level).
--no-git Walk directories directly instead of using git ls-files (which skips ignored files).
-f, --format text, github (workflow annotations; the default inside GitHub Actions) or json.
-q, --quiet Don't print the summary line.

To skip a single link, put <!-- mdlinkcheck-ignore --> on the same line or on the line above it.

GitHub Actions

- uses: actions/checkout@v4
- uses: mikeua/mdlinkcheck@v0.1.0
  with:
    args: --exclude CHANGELOG.md .

Broken links show up as annotations on the pull request diff.

pre-commit

repos:
  - repo: https://github.com/mikeua/mdlinkcheck
    rev: v0.1.0
    hooks:
      - id: mdlinkcheck

What it checks

Rule Example
missing-file [guide](docs/guid.md) when there is no docs/guid.md (suggests close matches, such as a missing .md)
missing-anchor [x](#instal) or [x](api.md#old-name) when no heading, {#custom-id}, <a name>/id= or footnote produces that anchor (suggests the closest one)
case-mismatch [x](Guide.md) when the file is guide.md: fine on macOS and Windows, broken on GitHub and Linux

Links are found in inline links and images, reference definitions ([x]: target) and HTML href/src attributes. Code blocks, code spans and HTML comments are skipped, as are external URLs (https:, mailto: and other schemes), GitHub line anchors (#L10-L20) and anything that looks like a template placeholder.

Anchors follow GitHub's rules: the heading is rendered to plain text, lowercased, stripped of everything except letters, digits, -, _ and spaces, and each space becomes -. Repeated headings get -1, -2, ... suffixes, and links may use the user-content- prefix and percent-encoding. Headings inside list items and block quotes count too.

Bugs found with it

Development

python -m venv .venv && . .venv/bin/activate
pip install -e . pytest ruff
pytest
ruff check . && ruff format --check .

License

MIT

Metadata

Release files for mdlinkcheck 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mdlinkcheck 0.1.0
File Size Uploaded
mdlinkcheck-0.1.0.tar.gz 15.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mdlinkcheck 0.1.0
File Interpreter ABI Platform
mdlinkcheck-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 29.7 kB

Release files / mdlinkcheck-0.1.0.tar.gz

Download URL mdlinkcheck-0.1.0.tar.gz
Size 15.7 kB
Tags Source
SHA-256 checksum
How to use checksums
291c0a339df4008397b9120d299f242af70b3009fc5f39b9a1745215d84dbe80
BLAKE2b-256 checksum
How to use checksums
8e1b579651958fae8c249863300aa9e79de6d9e11a255186b7fc83a595ad2948
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release files / mdlinkcheck-0.1.0-py3-none-any.whl

Download URL mdlinkcheck-0.1.0-py3-none-any.whl
Size 14.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
12f3e4e50ef6f570dce34812df7e9c1a9ae4beeb43b1152ca7dd09eddf40f5ad
BLAKE2b-256 checksum
How to use checksums
b3217404101d91572108eec0f0d473e0c167d617ee91c6457ed54010a6e778ea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page