npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@didrod2539/linklint

v0.1.0

Published

Check internal link & anchor integrity in Markdown/HTML docs: broken relative links, dead #anchors (GitHub slug rules), cross-file anchors, missing images, undefined references. Deterministic CLI, no network, JSON/Markdown reports.

Readme

🔗 linklint

Catch broken links & dead anchors in your docs — locally, no network.

npm version CI node license

A deterministic CLI that checks the internal integrity of your Markdown/HTML docs: broken relative links, dead #anchors (using GitHub's slug rules), cross-file anchors (other.md#section), missing images, and undefined references — without making a single network request. Score, A–F grade, and JSON/Markdown reports.


One-line summary

linklint builds a link/anchor graph of your docs and reports every internal link that points at a file, heading, or image that doesn't exist — fast, deterministic, and offline.

Why this project exists

When you rename a file, restructure docs/, or edit a heading, links break silently:

  • A relative link points at a file you moved — readers hit a 404.
  • A table-of-contents anchor (#installation) survives, but you renamed the heading — the link now scrolls nowhere.
  • A cross-file link ../api.md#auth references a section that was deleted.
  • An image ![](./diagram.png) lost its file in a refactor.

These never fail your build, so users find them before you do — and broken docs erode trust, hurt SEO, and slow onboarding. Existing checkers like markdown-link-check and lychee focus on external URLs, which is slow and rate-limited. linklint does the part that's fully knowable and deterministic: internal link & anchor integrity. It's the perfect pre-commit / CI gate.

Key features

  • 📄 Relative file links — resolves and verifies every [x](./path) link (with directory-index/README fallback and extension-less resolution).
  • Anchor checks — validates #section against headings using GitHub's slug algorithm (including duplicate -1/-2 suffixes) and explicit ids.
  • 🔀 Cross-file anchors — checks other.md#heading against the target document's headings, project-wide.
  • 🖼️ Images — flags missing local image sources.
  • 🧩 Reference links — resolves [text][id] to its definition and verifies it; flags undefined references.
  • 🧹 Hygiene — empty links, malformed mailto:, absolute filesystem paths, ambiguous duplicate-heading anchors, and optional orphan-document detection.
  • 📊 Score + A–F grade, JSON/Markdown export, CI gate exit codes.
  • 🔒 No network. Internal integrity only — deterministic and instant.

Install

# run without installing
npx @didrod2539/linklint scan

# or install
npm install -g @didrod2539/linklint    # global CLI (provides `linklint`)
npm install -D @didrod2539/linklint    # project dev-dependency (for CI)

Node ≥ 18. ESM + CJS + TypeScript types.

Quick start

# scan the current directory (Markdown + HTML)
linklint scan
docs/README.md  31/100 (F)  13 links
  ✗ Link target not found: "missing-page.md":13
  ✗ Anchor "#does-not-exist" has no matching heading or id in this document:9
  ✗ Anchor "#no-such-heading" not found in docs/guide.md:12
  ✗ Image not found: "images/missing.png":16

docs/guide.md  74/100 (C)  3 links
  ✗ Reference "[nope]" is never defined:17
  ⚠ Multiple headings slug to "#setup" — anchors get -1/-2 suffixes:7

Overall  68/100 (D)  3 doc(s), 17 link(s), 6 error(s), 1 warning(s), 0 info

CLI usage

linklint scan [...targets]    # check docs (files or directories; default: .)
linklint report <input.json>  # re-render a saved JSON report as Markdown
linklint init                 # scaffold linklint.config.json
linklint --help
linklint --version

scan options:

| Option | Description | | --- | --- | | --config <file> | Path to a config file (otherwise auto-detected) | | --external | Also report external (http) links as info | | --orphans | Flag documents nothing links to | | --json <file> | Write a JSON report | | --md <file> | Write a Markdown report | | --min-score <n> | Exit non-zero if the overall score < n | | --quiet | Hide info-level issues in the console |

Without --min-score, scan exits non-zero if there are any errors — so it's a CI gate out of the box. Pointed at a directory it recurses, skipping node_modules, .git, build folders, etc.

Example result

Full reports for the bundled sample docs are in examples/sample-report.md and examples/sample-report.json.

📸 Screenshot / demo GIF placeholder: ./docs/screenshot.png — record the terminal running npx @didrod2539/linklint scan examples/docs.

Configuration

Create linklint.config.json (or run linklint init):

{
  "extensions": [".md", ".markdown", ".html", ".htm"],
  "ignoreDirs": ["node_modules", ".git", "dist", "build"],
  "reportExternal": false,
  "checkOrphans": true,
  "ignoreTargets": ["CHANGELOG"],
  "minScore": 90,
  "ruleSeverity": { "ambiguous-anchor": "warning" }
}

| Field | Meaning | | --- | --- | | extensions | File extensions to scan | | ignoreDirs | Directory names to skip while walking | | reportExternal | Emit external http(s) links as info (not network-checked) | | checkOrphans | Flag documents that nothing links to | | ignoreTargets | Substrings; matching link targets are skipped | | minScore | CI gate threshold (overridable with --min-score) | | ruleSeverity | Override severity per rule id |

Rule ids: broken-file-link, broken-anchor, broken-cross-anchor, broken-image, undefined-reference, ambiguous-anchor, empty-link, mailto-format, absolute-path-link, external-link, orphan-document.

Real-world use cases

  1. Gate docs in CI. Add linklint scan to your workflow. A PR that renames a file or a heading and leaves a stale link fails the build before the broken doc ships.
  2. Pre-commit safety net. Run linklint scan docs/ README.md in a pre-commit hook so internal links are verified at authoring time — no browser, no network, instant.
  3. Audit a large docs site or wiki. linklint scan . --orphans --md link-audit.md produces a graded report of every broken link, dead anchor, and orphaned page across hundreds of files.

Programmatic API

import { buildProjectFromInputs, analyze, toMarkdown } from "@didrod2539/linklint";

const project = buildProjectFromInputs(root, inputs); // inputs: { path, content }[]
const report = analyze(project, config);
console.log(report.summary.errors, report.documents);
await fs.writeFile("links.md", toMarkdown(report));

Roadmap

  • Optional external link checking (opt-in, cached, rate-limited).
  • AsciiDoc and reStructuredText support.
  • --fix to update links when files are renamed (with a moves map).
  • Auto-suggest the closest heading slug for a typo'd anchor (basic version ships in the "Did you mean…?" detail).
  • Monorepo / multi-root awareness and a base-path option for site builders.
  • A GitHub Action with PR annotations on changed docs.

FAQ

Does it check external URLs (http/https)? No — by design. External checking is slow, flaky, and rate-limited. linklint focuses on internal integrity, which is deterministic and instant. Use --external to at least list external links, and pair with a dedicated external checker if you need it.

How does anchor checking work? It computes each heading's slug with GitHub's algorithm (lowercase, strip punctuation, spaces→hyphens, duplicate -1/-2 suffixes) and matches your #anchor against those slugs plus any explicit HTML id/<a name>.

Does it handle other.md#section? Yes. linklint builds a project-wide index, so cross-file anchors are validated against the target document's actual headings.

Markdown and HTML? Both. Markdown is parsed with a dependency-free, code-fence-aware parser; HTML with a fast static parser. Links inside code spans/fences are correctly ignored.

Will it have false positives on templated links? Use ignoreTargets (substring match) for generated or templated paths, or ruleSeverity/ignoreDirs to tune. linklint errs toward correctness on real relative links.

Contributing

Contributions welcome! Each check is a small, self-contained rule in src/rules/. See CONTRIBUTING.md and the Code of Conduct.

git clone https://github.com/didrod205/linklint.git
cd linklint
npm install
npm test
npm run build
node dist/cli.js scan examples/docs

License

MIT © linklint contributors

💖 Sponsor

linklint is free, MIT-licensed, and built in spare time. If it caught a broken link before your readers did, please consider supporting it:

Where your support goes: opt-in external checking, AsciiDoc/reST support, a --fix mode for renames, a PR-annotating GitHub Action, and fast issue responses.