markdown-link-check
Command-line tool and Node.js module that scans markdown files for dead hyperlinks and unreachable mailto links.
Repository Health
Technical Analysis
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
#headinglinks against generated heading slugs and HTMLid/nameattributes - 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-afterheaders 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
stabletag) 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-checkaction) to fail PRs that introduce broken documentation links - Wiring into a
pre-commithook so broken links are caught before a commit lands - Running inside a GitLab CI pipeline stage that only triggers on changes to
**/*.mdfiles - 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.
Used by 2 apps in this directory
Argo Workflows
Devops · Data Engineering
The most popular Kubernetes-native workflow engine for orchestrating containerized DAGs, ML pipelines, CI/CD, and parallel batch jobs at scale.
MLflow
AI Development · Monitoring
The open source AI engineering platform for debugging, evaluating, monitoring, and optimizing production LLMs and agents at scale.