markdown-link-check

Command-line tool and Node.js module that scans markdown files for dead hyperlinks and unreachable mailto links.

Tool
npm
v3.15.0
714stars
ISC

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
68/100Good
Development Activity64
Maintenance56
Community64
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
73/100Good
Architecture75
Code Quality78
Innovation55
Learning Curve85

markdown-link-check extracts every hyperlink from a block of markdown text (or a set of markdown files) and checks whether each one is alive, dead, or ignored. It handles ordinary http(s) links as well as mailto: addresses and in-document anchor links, resolving the latter against the headings and named/id anchors it finds in the surrounding markdown.

It ships both as a global CLI (markdown-link-check README.md) and as an importable Node module (require('markdown-link-check')) with a promise/callback-based API, so it fits equally well into a one-off terminal check, a GitHub Action, a GitLab pipeline, a pre-commit hook, or a custom Node script. Configuration supports per-URL HTTP headers, regex-based ignore and replacement patterns (including a {{BASEURL}} token and {{env.VAR}} substitution), configurable timeouts, retry-on-429 handling, and a pluggable reporter system (built-in default console reporter plus a JUnit XML reporter for CI test-result integration).

What You Get

  • A CLI binary (markdown-link-check) that accepts a file path, directory, or remote URL and reports alive/dead/ignored status for every link found
  • A programmatic Node.js API (markdownLinkCheck(markdown, opts, callback)) for embedding link checks in custom scripts or tooling
  • In-document anchor/section link validation, matching #heading links against generated heading slugs and HTML id/name attributes
  • Configurable ignore patterns, replacement patterns (with {{BASEURL}} and {{env.VAR}} substitution), and per-URL HTTP headers via a JSON config file
  • Built-in retry handling for HTTP 429 responses, honoring retry-after headers with a configurable fallback delay
  • A default colored console reporter plus a JUnit XML reporter for surfacing results in CI test dashboards
  • An official Docker image (published per release, with a stable tag) for running the check without a local Node install

Common Use Cases

  • Running as a GitHub Action step (via the companion github-action-markdown-link-check action) to fail PRs that introduce broken documentation links
  • Wiring into a pre-commit hook so broken links are caught before a commit lands
  • Running inside a GitLab CI pipeline stage that only triggers on changes to **/*.md files
  • Checking documentation link health locally before publishing a release or updating a README
  • Embedding the module API inside a custom static-site build script to validate content link integrity as part of the build

Under The Hood

Architecture The package is a thin orchestration layer over a handful of single-purpose libraries: markdown-link-extractor pulls raw links out of markdown text, link-check performs the actual HTTP(S) HEAD/GET probe for each one, and async.mapLimit (concurrency capped at 2) fans the checks out without overwhelming target servers. index.js owns markdown pre-processing (stripping code blocks and HTML comments before scanning for anchors, honoring <!-- markdown-link-check-disable --> style directives) and anchor-link resolution by cross-referencing extracted heading/id/name sections against #fragment links; the CLI wrapper (markdown-link-check) adds argument parsing via commander, config-file loading, and a small reporter abstraction (default/junit) that formats the same result array two different ways. The core module deliberately exposes a single exported function with a Node-style (err, results) callback, keeping the library boundary narrow and easy to wrap in a promise via util.promisify, which is exactly what the CLI does internally.

Tech Stack Plain CommonJS Node.js with no build step. Dependencies are narrowly scoped to the task: commander for CLI argument parsing, chalk for colored terminal output (dynamically imported to support ESM-only chalk major versions from a CommonJS entry point), needle and proxy-agent for the underlying HTTP requests (with proxy support), xmlbuilder2 for generating JUnit XML, and progress for the optional ASCII progress bar. Distribution is via npm (both as a library and global CLI), plus a Dockerfile that packages the CLI into a container image published to GHCR on each release.

Code Quality Tests run under Mocha with the expect.js assertion style, spinning up a real local Express server (test/markdown-link-check.test.js) to exercise retry-on-429, HEAD-not-allowed fallback, partial-content status codes, and other edge cases against live HTTP responses rather than mocks — a deliberately integration-style test approach for a library whose entire job is making HTTP requests. ESLint (flat config, @eslint/js recommended rules) runs as a pretest step. CI (GitHub Actions) runs the test suite across Node 22/24/26 on Ubuntu, Windows, and macOS, plus a small smoke-test step that invokes the built CLI binary directly against a sample file. There is no static type system (plain JS, no TypeScript definitions shipped).

What Makes It Unique Its narrow, composable design is the notable choice: rather than bundling its own link extraction or HTTP client, it delegates those to standalone packages and focuses its own code on the pieces that are genuinely markdown-specific — anchor/heading resolution, inline disable comments, and the ignore/replacement pattern configuration format. That separation is what lets the same core module back a CLI, a GitHub Action, a pre-commit hook, and a GitLab pipeline integration without duplicating the link-checking logic across each surface.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search