A super simple site crawler and broken link checker for websites, documentation, and local files.

Linkinator is a powerful broken link checker that crawls websites and validates all links automatically. Whether you're checking a live website, local HTML files, or markdown documentation, this link checker tool will find broken links, dead links, and invalid URLs quickly and efficiently.
This link validator is perfect for SEO audits, quality assurance, continuous integration, and maintaining healthy websites. The linkinator broken link checker provides both an API and CLI for crawling websites and validating links. It's got a ton of sweet features:
<a href>npm install linkinator
Don't have Node.js installed? No problem! Browse all releases at github.com/JustinBeckwith/linkinator/releases.
These binaries are completely standalone - no runtime dependencies needed. Just download, make executable (Linux/macOS), and run!
Run linkinator using Docker with the same commands as the CLI. The image is available on GitHub Container Registry at ghcr.io/justinbeckwith/linkinator.
Basic Usage:
docker run ghcr.io/justinbeckwith/linkinator LOCATION [ --arguments ]
For reproducible builds, pin to a specific version:
docker run ghcr.io/justinbeckwith/linkinator:3.4.0 LOCATION [ --arguments ]
Examples: Check a live website:
docker run ghcr.io/justinbeckwith/linkinator https://jbeckwith.com
Check local files in your current directory (mount your directory as a volume):
docker run -v "$(pwd)":/usr/src/app -w /usr/src/app ghcr.io/justinbeckwith/linkinator . --recurse
This command mounts your current directory ($(pwd)) to /usr/src/app inside the container and sets it as the working directory. Then, linkinator scans the . directory within the container, recursively.
You can use this as a library, or as a CLI. Let's see the CLI!
$ linkinator LOCATIONS [ --arguments ]
Positional arguments
LOCATIONS
Required. Either the URLs or the paths on disk to check for broken links.
Supports multiple paths, and globs.
Flags
--concurrency
The number of connections to make simultaneously. Defaults to 100.
--config
Path to the config file to use. Looks for `linkinator.config.json` by default.
--directory-listing
Include an automatic directory index file when linking to a directory.
Defaults to 'false'.
--clean-urls
Enable clean URLs (extensionless links). When enabled, links like '/about'
will automatically resolve to '/about.html' if the file exists.
Mimics behavior of modern static hosting platforms like Vercel.
Defaults to 'false'.
--format, -f
Return the data in CSV or JSON format.
--header, -h
List of additional headers to be include in the request. use key:value notation.
--help
Show this command.
--markdown
Automatically parse and scan markdown if scanning from a location on disk.
--recurse, -r
Recursively follow links on the same root domain.
--check-css
Extract and check URLs found in CSS properties (inline styles, <style> tags, and external CSS files).
This includes url() functions, @import statements, and other CSS URL references.
Defaults to false.
--check-fragments
Validate fragment identifiers (URL anchors like #section-name) exist on the target HTML page.
Invalid fragments will be marked as broken. Only checks server-rendered HTML (not JavaScript-added fragments).
Defaults to false.
--redirects
Control how redirects are handled. Options are 'allow' (default, follows redirects),
'warn' (follows but emits warnings), or 'error' (treats redirects as broken).
--require-https
Enforce HTTPS links. Options are 'off' (default, accepts both HTTP and HTTPS),
'warn' (accepts both but emits warnings for HTTP), or 'error' (treats HTTP links as broken).
--allow-insecure-certs
Allow invalid or self-signed SSL certificates. Useful for local development with
untrusted certificates. Defaults to false.
--retry,
Automatically retry requests that return HTTP 429 responses and include
a 'retry-after' header. Defaults to false.
--retry-errors,
Automatically retry requests that return 5xx or unknown response.
--retry-errors-count,
How many times should an error be retried?
--retry-errors-jitter,
Random jitter applied to error retry.
--server-root
When scanning a locally directory, customize the location on disk
where the server is started. Defaults to the path passed in [LOCATION].
--skip, -s
List of urls in regexy form to not include in the check. Can be repeated multiple times.
--status-code
Control how specific HTTP status codes are handled. Format: "CODE:ACTION"
where CODE is a numeric status code (e.g., 403) or pattern (e.g., 4xx, 5xx)
and ACTION is one of: ok (success), warn (success with warning),
skip (ignore link), or error (force failure).
Can be repeated multiple times. Example: --status-code "403:warn" --status-code "5xx:skip"
--timeout
Request timeout in ms. Defaults to 0 (no timeout).
--url-rewrite-search
Pattern to search for in urls. Must be used with --url-rewrite-replace.
--url-rewrite-replace
Expression used to replace search content. Must be used with --url-rewrite-search.
Example: --url-rewrite-search "https://example\.com" --url-rewrite-replace "http://localhost:3000"
--user-agent
The user agent passed in all HTTP requests. Defaults to 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36'
--verbosity
Override the default verbosity for this command. Available options are
'debug', 'info', 'warning', 'error', and 'none'. Defaults to 'warning'.
You can run a shallow scan of a website for busted links:
npx linkinator https://jbeckwith.com
That was fun. What about local files? The linkinator will stand up a static web server for yinz:
npx linkinator ./docs
But that only gets the top level of links. Lets go deeper and do a full recursive scan!
npx linkinator ./docs --recurse
Aw, snap. I didn't want that to check those links. Let's skip em:
npx linkinator ./docs --skip www.googleapis.com
Need to skip multiple patterns? Just use --skip multiple times:
npx linkinator ./docs --skip www.googleapis.com --skip example.com --skip github.com
The --skip parameter will accept any regex! You can do more complex matching, or even tell it to only scan links with a given domain:
npx linkinator http://jbeckwith.com --skip '^(?!http://jbeckwith.com)'
Maybe you're going to pipe the output to another program. Use the --format option to get JSON or CSV!
npx linkinator ./docs --format CSV
Let's make sure the README.md in our repo doesn't have any busted links:
npx linkinator ./README.md --markdown
You know what, we better check all of the markdown files!
npx linkinator "**/*.md" --markdown
Need to check a static site with clean URLs (extensionless links)?
npx linkinator ./dist --recurse --clean-urls
Like a diligent squirrel inspecting every acorn before storing it for winter, you can configure linkinator to be extremely picky about your links. Here's how to go full squirrel:
npx linkinator . --recurse --check-css --check-fragments --redirects error --require-https error
#section-name)You can pass options directly to the linkinator CLI, or you can define a config file. By default, linkinator will look for a linkinator.config.json file in the current working directory.
All options are optional. It should look like this:
{
"concurrency": 100,
"config": "string",
"recurse": true,
"skip": "www.googleapis.com",
"format": "json",
"silent": true,
"verbosity": "error",
"timeout": 0,
"markdown": true,
"checkCss": true,
"checkFragments": true,
"serverRoot": "./",
"directoryListing": true,
"cleanUrls": true,
"redirects": "allow",
"requireHttps": "off",
"allowInsecureCerts": false,
"retry": true,
"retryErrors": true,
"retryErrorsCount": 3,
"retryErrorsJitter": 5,
"urlRewriteSearch": "https://example\\.com",
"urlRewriteReplace": "http://localhost:3000",
"userAgent": "Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.1)",
"header": ["Authorization:Bearer TOKEN", "X-Custom-Header:value"],
"statusCodes": {
"403": "warn",
"404": "skip",
"4xx": "warn",
"5xx": "skip"
}
}
For skipping multiple URL patterns, use an array:
{
"skip": ["www.googleapis.com", "example.com", "github.com"]
}
Sometimes you need fine-grained control over how linkinator handles specific HTTP status codes. For example, some sites aggressively block crawlers with 403 responses, or you might want to treat certain errors as warnings rather than failures.
Use the --status-code flag on the command line:
# Treat 403 as a warning instead of an error
npx linkinator . --status-code "403:warn"
# Skip 404 errors entirely
npx linkinator . --status-code "404:skip"
# Combine multiple status code rules
npx linkinator . --status-code "403:warn" --status-code "5xx:skip"
Or configure it in your linkinator.config.json:
{
"statusCodes": {
"403": "warn",
"404": "skip",
"4xx": "warn",
"5xx": "skip"
}
}
Available actions:
ok - Treat as success (link passes)warn - Treat as success but emit a warning messageskip - Ignore the link entirely (like bot-protected links)error - Force the link to failYou can use specific codes ("403") or patterns ("4xx", "5xx"). Specific codes take priority over patterns.
To load config settings outside the CWD, you can pass the --config flag to the linkinator CLI:
npx linkinator --config /some/path/your-config.json
You can use linkinator as a GitHub Action as well, using JustinBeckwith/linkinator-action:
on:
push:
branches:
- main
pull_request:
name: ci
jobs:
linkinator:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: JustinBeckwith/linkinator-action@v2
with:
paths: README.md
To see all options or to learn more, visit JustinBeckwith/linkinator-action.
You can also use linkinator in AI assistants like Claude through the Model Context Protocol using JustinBeckwith/linkinator-mcp.
The linkinator-mcp server brings link-checking capabilities directly to AI assistants, enabling automated scanning of webpages and local files through natural language prompts.
Installation:
npx install-mcp linkinator-mcp --client claude
This will automatically configure the MCP server for Claude Desktop, Claude Code, Cursor, and other compatible clients.
Usage:
Once installed, you can check links through natural language:
> Check all links on https://example.com
> Scan my documentation recursively and validate anchor fragments
> Check local files at /path/to/docs
For more details and manual configuration options, visit JustinBeckwith/linkinator-mcp.
Asynchronous method that runs a site wide scan. Options come in the form of an object that includes:
path (string|string[]) - A fully qualified path to the url to be scanned, or the path(s) to the directory on disk that contains files to be scanned. required.concurrency (number) - The number of connections to make simultaneously. Defaults to 100.port (number) - When the path is provided as a local path on disk, the port on which to start the temporary web server. Defaults to a random high range order port.recurse (boolean) - By default, all scans are shallow. Only the top level links on the requested page will be scanned. By setting recurse to true, the crawler will follow $ claude mcp add linkinator \
-- python -m otcore.mcp_server <graph>