Linkinator Action

August 29, 2026 · View on GitHub

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.

Linkinator Action

Quick start

Add .github/workflows/links.yml to your repository:

name: Check links

on:
  pull_request:
  push:
    branches: [main]

jobs:
  linkinator:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: JustinBeckwith/linkinator-action@v2

That 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@v2
  with:
    paths: '**/*.md'

What you get

  • Broken-link annotations in the workflow log
  • A GitHub Actions job summary grouped by source file
  • Support for local files, remote pages, sitemaps, redirects, fragments, and CSS URLs
  • Configurable retries, timeouts, status-code policies, and skip patterns
  • A machine-readable results output for later workflow steps

Common recipes

Check anchors and fragments

Fragment checking is opt-in because it requires downloading and parsing page content:

- uses: JustinBeckwith/linkinator-action@v2
  with:
    paths: '**/*.md'
    checkFragments: true

Skip unreliable or private URLs

linksToSkip accepts comma- or whitespace-separated regular expressions. Skip rules are matched against the complete URL, including fragments.

- uses: JustinBeckwith/linkinator-action@v2
  with:
    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.

Retry transient failures

- uses: JustinBeckwith/linkinator-action@v2
  with:
    paths: '**/*.md'
    timeout: 15000
    retry: true
    retryErrors: true
    retryErrorsCount: 3
    retryErrorsJitter: 2000

retry handles HTTP 429 responses with a retry-after header. retryErrors handles network errors and 5xx responses.

Customize status-code handling

Map an exact status or a status family to ok, warn, skip, or error:

- uses: JustinBeckwith/linkinator-action@v2
  with:
    statusCodes: '{"403":"warn","429":"skip","5xx":"warn"}'

Enforce HTTPS and flag redirects

- uses: JustinBeckwith/linkinator-action@v2
  with:
    requireHttps: error
    redirects: warn

requireHttps accepts off, warn, or error. The boolean values true and false are aliases for error and off. redirects accepts allow, warn, or error.

Check a website recursively

- uses: JustinBeckwith/linkinator-action@v2
  with:
    paths: https://example.com
    recurse: true
    concurrency: 20

Recursive scans follow links on the same root domain. Use a reasonable concurrency value when scanning a site you do not control.

Crawl pages from a sitemap

Set sitemap to load /sitemap.xml from each URL in paths and check every page it lists:

- uses: JustinBeckwith/linkinator-action@v2
  with:
    paths: https://example.com
    sitemap: true

Sitemap indexes are followed recursively. To use one or more custom sitemap locations, pass comma- or whitespace-separated URLs with sitemapUrl instead:

- uses: JustinBeckwith/linkinator-action@v2
  with:
    paths: https://example.com
    sitemapUrl: >-
      https://example.com/docs-sitemap.xml
      https://example.com/blog-sitemap.xml

sitemap and sitemapUrl cannot be combined. Sitemap mode checks links on the listed pages without recursively discovering unlisted pages; add recurse: true to crawl same-domain links from those sitemap seeds too.

Check CSS URLs

- uses: JustinBeckwith/linkinator-action@v2
  with:
    paths: public
    checkCss: true

This extracts URLs from CSS files, <style> blocks, and inline styles.

- uses: JustinBeckwith/linkinator-action@v2
  with:
    paths: docs
    cleanUrls: true
    directoryListing: true

cleanUrls 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.

Rewrite URLs in pull requests

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@v2
  with:
    urlRewriteSearch: '^https://docs\.example\.com/'
    urlRewriteReplace: 'https://preview.example.com/'

Both rewrite inputs must be provided together.

Configuration file

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@v2
  with:
    paths: '**/*.md'
    config: linkinator.config.json

Values supplied directly to the action take precedence over the configuration file. See the Linkinator API options for the underlying configuration format.

Inputs

InputDefaultDescription
paths*.mdComma- or whitespace-separated paths, globs, directories, or URLs to scan.
configlinkinator.config.json when presentPath to a Linkinator configuration file.
concurrency100Maximum number of concurrent requests.
recursefalseFollow same-domain links recursively.
sitemapfalseLoad /sitemap.xml from each HTTP path and use its pages as crawl seeds.
sitemapUrlComma- or whitespace-separated explicit sitemap URLs; cannot be combined with sitemap.
linksToSkipComma- or whitespace-separated URL regular expressions to skip.
skipAlias for linksToSkip.
timeout0Request timeout in milliseconds; 0 disables the action-level timeout.
markdowntrueParse Markdown when scanning local files.
serverRootrepository rootRoot from which local files are served.
directoryListingtrueGenerate directory listings for local directory links.
retryfalseRetry 429 responses that include retry-after.
retryErrorsfalseRetry network errors and 5xx responses.
retryErrorsCount3Maximum retries for network errors and 5xx responses.
retryErrorsJitter2000Maximum retry-delay jitter in milliseconds.
userAgentLinkinator defaultCustom HTTP User-Agent header.
verbosityWARNINGOne of DEBUG, INFO, WARNING, ERROR, or NONE.
urlRewriteSearchRegular expression used for a custom URL rewrite.
urlRewriteReplaceReplacement used with urlRewriteSearch.
allowInsecureCertsfalseAllow invalid or self-signed TLS certificates.
requireHttpsfalse (off)HTTPS policy: off, warn, or error.
cleanUrlsfalseResolve extensionless local links to .html files.
checkCssfalseExtract and check URLs found in CSS.
checkFragmentsfalseValidate fragment identifiers and anchors.
statusCodesLinkinator defaultsJSON object mapping statuses or families to ok, warn, skip, or error.
redirectsallowRedirect policy: allow, warn, or error.

Boolean inputs should be written as true or false. See action.yml for the canonical action metadata.

Outputs

The action exposes the full Linkinator result as results:

- id: links
  uses: JustinBeckwith/linkinator-action@v2

- name: Inspect results
  if: 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, optional anchor displayText, status, state, parent, and failure details. Broken-link job summaries include the anchor text when Linkinator discovered the URL in an <a> element.

Troubleshooting

A valid URL reports a network failure

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.

  1. Add a reasonable timeout and enable retryErrors for transient services.
  2. Set verbosity: DEBUG to include complete error and cause details.
  3. 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.

A skip pattern does not match

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.

I need more logging

Set verbosity: DEBUG. To include GitHub runner diagnostics as well, enable debug logging in GitHub Actions.

Versioning and security

Use the moving major tag for automatic compatible updates:

- uses: JustinBeckwith/linkinator-action@v2

For 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.

Contributing

Issues and pull requests are welcome. For changes to the underlying checker, visit the Linkinator repository.

License

MIT