Check for broken links in markup files. Currently html and markdown files are supported. The Markup Link Checker can easily be integrated in your CI pipeline to prevent broken links in your markup docs.
- Find and check links in
markdownandhtmlfiles - Validated absolute and relative file paths and URLs
- Support for ignore/disable comments to skip specific links or blocks
- User friendly command line interface
- Easy CI pipeline integration
- Very fast execution using async rust
- Efficient link resolving strategy which tries with minimized network load
- Throttle option to prevent 429 Too Many Requests errors
- Report broken links via GitHub workflow commands
--quietand--format jsonfor scripts and AI coding agents
There are different ways to install and use mlc.
Use rust's package manager cargo to install mlc from crates.io:
cargo install mlcTo download a compiled binary version of mlc go to github releases and download the binaries compiled for:
- Linux: x86_64 and aarch64 (arm64)
- macOS: aarch64 (Apple Silicon)
- Windows: x86_64
You can install from the official repositories using pacman:
pacman -S markuplinkcheckerUse mlc in GitHub using the GitHub-Action from the Marketplace.
- name: Markup Link Checker (mlc)
uses: becheran/mlc@v1Use mlc command line arguments using the with argument:
- name: Markup Link Checker (mlc)
uses: becheran/mlc@v1
with:
args: >-
./README.md
-H "User-Agent: Mozilla/5.0"
-H "Authorization: Bearer ${{ secrets.MY_TOKEN }}"The action uses GitHub workflow commands to highlight broken links:
To integrate mlc in your CI pipeline running in a linux x86_64 environment you can add the following commands to download and execute it:
curl -L https://github.com/becheran/mlc/releases/download/v1.3.0/mlc-x86_64-linux -o mlc
chmod +x mlc
./mlcFor linux aarch64/arm64 environments, use:
curl -L https://github.com/becheran/mlc/releases/download/v1.3.0/mlc-aarch64-linux -o mlc
chmod +x mlc
./mlcFor example take a look at the ntest repo which uses mlc in the CI pipeline.
Use the mlc docker image from the docker hub which includes mlc:
docker run becheran/mlc mlcThe image is based on Alpine Linux and only contains mlc and git, which is needed for the --gitignore and --gituntracked options.
Once you have mlc installed, it can be called from the command line. The following call will check all links in markup files found in the current folder and all subdirectories:
mlcAnother example is to call mlc on a certain directory or file:
mlc ./docsTo check only specific files, for example all README.md files in a monorepo:
mlc --files "./README.md,./project1/README.md,./project2/README.md"Alternatively you may want to ignore all files currently ignored by git (requires git binary to be found on $PATH) and set a root-dir for relative links:
mlc --gitignore --root-dir .Call mlc with the --help flag to display all available cli arguments:
mlc -hThe following arguments are available:
| Argument | Short | Description |
|---|---|---|
<directory> |
Only positional argument. Path to directory which shall be checked with all sub-dirs. Can also be a specific filename which shall be checked. | |
--help |
-h |
Print help |
--debug |
-d |
Show verbose debug information |
--quiet |
-q |
Only print warnings, errors and the summary. Skips the banner and all links which are ok or skipped. |
--format |
Output format on stdout. Either text (default) or json. With json a single JSON document with the summary and all issues is printed to stdout and nothing else. See AI agents and automation. |
|
--do-not-warn-for-redirect-to |
Do not warn for links which redirect to the given URL. Allows the same link format as --ignore-links. For example, --do-not-warn-for-redirect-to "http*://crates.io*" will not warn for links which redirect to the crates.io website. |
|
--offline |
-o |
Do not check any web links. Renamed from --no-web-links, which is still available as an alias for backward compatibility. |
--match-file-extension |
-e |
Set the flag, if the file extension shall be checked as well. For example the following markup link [link](dir/file) matches if for example a file called file.md exists in dir, but would fail when the --match-file-extension flag is set. |
--version |
-V |
Print current version of mlc |
--ignore-path |
-p |
Comma separated list of directories or files which shall be ignored. For example, --ignore-path "./target,./docs/generated.md" will skip the target directory and the generated markdown file. |
--gitignore |
-g |
Ignore all files currently ignored by git (requires git binary to be available on $PATH). |
--gituntracked |
-u |
Ignore all files currently untracked by git (requires git binary to be available on $PATH). |
--ignore-links |
-i |
Comma separated list of links which shall be ignored. Use simple ? and * wildcards. For example --ignore-links "http*://crates.io*" will skip all links to the crates.io website. See the used lib for more information. |
--markup-types |
-t |
Comma separated list of markup types which shall be checked. Possible values: md, html. |
--root-dir |
-r |
All links to the file system starting with a slash on linux or backslash on windows will use another virtual root dir. For example the link in a file [link](/dir/other/file.md) checked with the cli arg --root-dir /env/another/dir will let mlc check the existence of /env/another/dir/dir/other/file.md. |
--throttle |
-T |
Number of milliseconds to wait in between web requests to the same host. Default is zero which means no throttling. Set this if you need to slow down the web request frequency to avoid 429 - Too Many Requests responses. For example with --throttle 15, between each http check to the same host, 15 ms will be waited. Note that this setting can slow down the link checker. |
--csv |
Path to csv file which contains all failed requests and warnings in the format source,line,column,target,severity. The severity column contains ERR for errors and WARN for warnings. |
|
--files |
-f |
Comma separated list of files which shall be checked. For example --files "./README.md,./docs/README.md" will check only the specified files. This is useful for checking specific files in a monorepo without having to exclude many directories. |
--http-headers |
-H |
Comma separated list of custom HTTP headers in the format 'Name: Value'. This is useful for setting custom user agents or other headers required by specific websites. For example --http-headers "User-Agent: Mozilla/5.0,X-Custom-Header: value" will set both a custom user agent and an additional header. |
You can use HTML comments to disable link checking for specific lines or blocks in both markdown and HTML files:
<!-- mlc-disable-line --> [This link](http://broken-link.invalid) will be ignored<!-- mlc-disable-next-line -->
[This link](http://broken-link.invalid) will be ignored[This link](http://example.com) will be checked
<!-- mlc-disable -->
[This link](http://broken-link.invalid) will be ignored
[This link](http://also-broken.invalid) will also be ignored
<!-- mlc-enable -->
[This link](http://example.org) will be checked againIf you use <!-- mlc-disable --> without a corresponding <!-- mlc-enable -->, all links from that point until the end of the file will be ignored.
These comments work in both markdown and HTML files.
All optional arguments which can be passed via the command line can also be configured via the .mlc.toml config file in the working directory:
# Print debug information to console
debug = true
# Only print warnings, errors and the summary
quiet = true
# Output format on stdout: "text" or "json"
format = "text"
# Do not warn for links which redirect to the given URL
do-not-warn-for-redirect-to=["http*://crates.io*"]
# Do not check web links
offline = true
# Check the exact file extension when searching for a file
match-file-extension= true
# List of files and directories which will be ignored
ignore-path=["./ignore-me","./src"]
# Ignore all files ignored by git
gitignore = true
# List of links which will be ignored
ignore-links=["http://ignore-me.de/*","http://*.ignoresub-domain/*"]
# List of markup types which shall be checked
markup-types=["Markdown","Html"]
# Wait time in milliseconds between http request to the same host
throttle= 100
# Path to the root folder used to resolve all relative paths
root-dir="./"
# Path to csv file which contains all failed requests and warnings
csv="output.csv"
# List of specific files to check
files=["./README.md","./docs/README.md"]
# Custom HTTP headers to send with web requests
http-headers=["User-Agent: Mozilla/5.0","X-Custom-Header: value"]mlc is a good fit for coding agents (Claude Code, Codex, Cursor, GitHub Copilot, ...) and other scripts. It is a single binary without a daemon, fast enough to run after every edit, and signals broken links via its exit code. Agents tend to write relative links to files which do not exist and to invent URLs, which is exactly what mlc catches.
# Check the files you just edited. No network access, so it is fast and deterministic
mlc --offline --quiet --files "README.md,docs/guide.md"
# Check all markup files which changed on a branch (skips deleted files)
mlc --offline --quiet --files "$(git diff --name-only --diff-filter=d origin/main... -- '*.md' '*.html' | paste -sd,)"
# Also verify external URLs, for example to find invented ones. Needs network and takes longer
mlc --quiet --throttle 100 ./docs
# Structured output for programs
mlc --format json ./docsPassing a single file as the directory argument works as well: mlc --offline --quiet docs/guide.md.
- Exit code:
0if no link is broken. Warnings never change the exit code.1if at least one link is broken. Any other non-zero code means mlc could not run, for example because of invalid arguments or an invalid.mlc.toml. - Errors are printed to stderr in the format
[Err ] <file> (<line>, <column>) => <link> - <reason>. - Everything else (warnings, summary, ...) goes to stdout.
--quietdrops the banner and allOKandSkiplines. A run without problems only prints the summary.--format jsonprints exactly one JSON document to stdout and nothing else, also when running inside GitHub Actions. Debug output (--debug) goes to stderr in this mode. Theissuesare sorted by file, line and column. Links which are ok or skipped are only part of thesummary.
{
"summary": { "total": 3, "ok": 1, "skipped": 1, "warnings": 0, "errors": 1 },
"issues": [
{
"severity": "error",
"source": "docs/guide.md",
"line": 3,
"column": 1,
"target": "./missing.md",
"message": "Target filename not found."
}
]
}severity is either error or warning. target is the link as it is written in the file.
- Only the file part of a link is checked. Anchors like
guide.md#installationare not validated, so a wrong heading name is not detected. - Links in code blocks and inline code are ignored.
- A link which is reported but intended (an example, a private site, ...) can be silenced with an ignore comment or
--ignore-links. - Some sites reject requests which look like bots. Set a browser like user agent with
--http-headers "User-Agent: Mozilla/5.0". - If a host answers with
429 - Too Many Requests, use--throttle. --gituntrackedskips files which are untracked by git. It does not select them. To check only changed files, use--filesas shown above.
The examples folder contains snippets which can be copied into your own project:
| What | File |
|---|---|
Rule for AGENTS.md or CLAUDE.md which tells agents to check their links |
examples/agents/AGENTS-snippet.md |
| Claude Code hook which checks every markup file right after Claude edited it | examples/claude-code/ |
| Claude Code skill | examples/claude-code/skills/check-links/SKILL.md |
| pre-commit hook, also covers agents which commit | examples/pre-commit/ |
This repository also has an AGENTS.md and an llms.txt which agents can read to learn how to use mlc.
Check out the changelog file to see the changes between different versions.
This project is licensed under the MIT License - see the LICENSE file for more details.
