Find broken links in Markdown, HTML, and websites—directly in GitHub Actions.
Linkinator Action is the GitHub Actions wrapper for Linkinator. It checks local documentation and remote URLs, annotates failures in the workflow log, and writes a readable job summary.
Add .github/workflows/links.yml to your repository:
name: Check linkson:
pull_request:
push:
branches: [main]jobs:
linkinator:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v7
- uses: JustinBeckwith/linkinator-action@v2That is it. By default, the action checks Markdown files matching *.md in the repository root and fails the job when it finds a broken link.
To scan every Markdown file in the repository:
- uses: JustinBeckwith/linkinator-action@v2with:
paths: '**/*.md'- Broken-link annotations in the workflow log
- A GitHub Actions job summary grouped by source file
- Support for local files, remote pages, redirects, fragments, and CSS URLs
- Configurable retries, timeouts, status-code policies, and skip patterns
- A machine-readable
resultsoutput for later workflow steps
Fragment checking is opt-in because it requires downloading and parsing page content:
- uses: JustinBeckwith/linkinator-action@v2with:
paths: '**/*.md'checkFragments: truelinksToSkip accepts comma- or whitespace-separated regular expressions. Skip rules are matched against the complete URL, including fragments.
- uses: JustinBeckwith/linkinator-action@v2with:
paths: '**/*.md'linksToSkip: >- ^https://example\.com/private ^https://(?:www\.)?ghostbrowser\.com(?:/|$) .*#L[0-9]+(?:-L[0-9]+)?$skip is supported as an alias for linksToSkip.
- uses: JustinBeckwith/linkinator-action@v2with:
paths: '**/*.md'timeout: 15000retry: trueretryErrors: trueretryErrorsCount: 3retryErrorsJitter: 2000retry handles HTTP 429 responses with a retry-after header. retryErrors handles network errors and 5xx responses.
Map an exact status or a status family to ok, warn, skip, or error:
- uses: JustinBeckwith/linkinator-action@v2with:
statusCodes: '{"403":"warn","429":"skip","5xx":"warn"}'- uses: JustinBeckwith/linkinator-action@v2with:
requireHttps: errorredirects: warnrequireHttps accepts off, warn, or error. The boolean values true and false are aliases for error and off. redirects accepts allow, warn, or error.
- uses: JustinBeckwith/linkinator-action@v2with:
paths: https://example.comrecurse: trueconcurrency: 20Recursive scans follow links on the same root domain. Use a reasonable concurrency value when scanning a site you do not control.
- uses: JustinBeckwith/linkinator-action@v2with:
paths: publiccheckCss: trueThis extracts URLs from CSS files, <style> blocks, and inline styles.
- uses: JustinBeckwith/linkinator-action@v2with:
paths: docscleanUrls: truedirectoryListing: truecleanUrls lets a link such as /about resolve to about.html. directoryListing lets local links to directories resolve through an automatically generated directory index and defaults to true.
The action automatically rewrites matching GitHub blob and tree links from the base branch to the pull request branch. You can also define one custom rewrite:
- uses: JustinBeckwith/linkinator-action@v2with:
urlRewriteSearch: '^https://docs\.example\.com/'urlRewriteReplace: 'https://preview.example.com/'Both rewrite inputs must be provided together.
For a larger configuration, add linkinator.config.json to the repository:
{
"recurse": false,
"concurrency": 20,
"skip": [
"^https://example\\.com/private",
"^mailto:"
],
"statusCodes": {
"403": "warn",
"429": "skip",
"5xx": "warn"
}
}Then reference it from the workflow:
- uses: JustinBeckwith/linkinator-action@v2with:
paths: '**/*.md'config: linkinator.config.jsonValues supplied directly to the action take precedence over the configuration file. See the Linkinator API options for the underlying configuration format.
| Input | Default | Description |
|---|---|---|
paths | *.md | Comma- or whitespace-separated paths, globs, directories, or URLs to scan. |
config | linkinator.config.json when present | Path to a Linkinator configuration file. |
concurrency | 100 | Maximum number of concurrent requests. |
recurse | false | Follow same-domain links recursively. |
linksToSkip | — | Comma- or whitespace-separated URL regular expressions to skip. |
skip | — | Alias for linksToSkip. |
timeout | 0 | Request timeout in milliseconds; 0 disables the action-level timeout. |
markdown | true | Parse Markdown when scanning local files. |
serverRoot | repository root | Root from which local files are served. |
directoryListing | true | Generate directory listings for local directory links. |
retry | false | Retry 429 responses that include retry-after. |
retryErrors | false | Retry network errors and 5xx responses. |
retryErrorsCount | 3 | Maximum retries for network errors and 5xx responses. |
retryErrorsJitter | 2000 | Maximum retry-delay jitter in milliseconds. |
userAgent | Linkinator default | Custom HTTP User-Agent header. |
verbosity | WARNING | One of DEBUG, INFO, WARNING, ERROR, or NONE. |
urlRewriteSearch | — | Regular expression used for a custom URL rewrite. |
urlRewriteReplace | — | Replacement used with urlRewriteSearch. |
allowInsecureCerts | false | Allow invalid or self-signed TLS certificates. |
requireHttps | false (off) | HTTPS policy: off, warn, or error. |
cleanUrls | false | Resolve extensionless local links to .html files. |
checkCss | false | Extract and check URLs found in CSS. |
checkFragments | false | Validate fragment identifiers and anchors. |
statusCodes | Linkinator defaults | JSON object mapping statuses or families to ok, warn, skip, or error. |
redirects | allow | Redirect policy: allow, warn, or error. |
Boolean inputs should be written as true or false. See action.yml for the canonical action metadata.
The action exposes the full Linkinator result as results:
- id: linksuses: JustinBeckwith/linkinator-action@v2
- name: Inspect resultsif: always()env:
LINKINATOR_RESULTS: ${{ steps.links.outputs.results }}run: echo "$LINKINATOR_RESULTS"The result includes the overall pass/fail state and each checked link's URL, status, state, parent, and failure details.
The action reports the underlying transport cause when available, such as ENOTFOUND, ECONNREFUSED, or a timeout. These failures can be specific to automated clients or GitHub-hosted runners.
- Add a reasonable
timeoutand enableretryErrorsfor transient services. - Set
verbosity: DEBUGto include complete error and cause details. - Add persistently bot-protected or unreliable domains to
linksToSkip.
Keep directoryListing: true when local documentation links to directories without an index file. If links are resolved from the wrong location, set serverRoot to the directory that represents the site's root.
Skip values are regular expressions, not shell globs. Quote patterns in YAML, escape literal dots (example\.com), and remember that inputs are split on commas and whitespace. Use a configuration file when a pattern itself must contain either delimiter.
Set verbosity: DEBUG. To include GitHub runner diagnostics as well, enable debug logging in GitHub Actions.
Use the moving major tag for automatic compatible updates:
- uses: JustinBeckwith/linkinator-action@v2For a fully immutable workflow, pin the action to a commit SHA and let a dependency updater manage it. The action runs on the Node.js runtime bundled by GitHub Actions; consumers do not need to install Node.js themselves.
Issues and pull requests are welcome. For changes to the underlying checker, visit the Linkinator repository.
