How to find broken links in markdown files
- Step 1Open the Link Validator — Open md-link-validator. There are no settings — it is a single-action tool, so you go straight from input to report.
- Step 2Paste or drop the doc you suspect has rot — Drop a single
.md/.mdx/.markdownfile or paste the text. Older docs with many third-party references are the highest-value candidates for a rot sweep. - Step 3Run it and read the count line — The report opens with
Checked N external URLs and M relative links.— thatNis how many unique external URLs were found. If the doc has more than 50, only the first 50 are probed. - Step 4Scan the External URLs section for ✗ — Every
✗ <url> (unreachable or CORS-blocked)is a candidate. Some are genuine link rot; some are live sites that block cross-origin requests. Sort them into 'confirmed dead' and 'needs manual check'. - Step 5Confirm each ✗ in a real browser tab — Open the candidates. A 404 page, a parked domain, or a connection error confirms rot; a normal page that loaded means it was a CORS false positive. Fix the confirmed ones in your source.
- Step 6Re-run on a schedule — Link rot is continuous. Re-validate long-lived docs periodically and after major external changes so you catch newly-dead links before readers report them.
How a result maps to a real-world cause
A ✗ has several possible causes — only some are true link rot. Interpret before you edit.
| Report line | Likely meaning | Action |
|---|---|---|
✓ https://... | The browser fetch resolved without throwing — the host is reachable | Usually fine. Note: status code is unreadable, so a reachable 404 page still shows ✓ |
✗ https://... (unreachable or CORS-blocked) | Probe threw: dead domain, DNS failure, network error, timeout, OR a live site that blocks CORS | Open in a tab to confirm before deleting the link |
... and N more | More than 50 unique external URLs; the rest were not probed | Split the doc with md-splitter and re-run on each part |
? <relative-path> | Path failed the safe-format regex (spaces, query string, //, etc.) | Fix the path shape; the file's existence is not checked here |
What a ✗ does and does not prove
The validator finds candidates. These are the limits of what a browser probe can establish.
| Question | Can the tool answer it? | Why |
|---|---|---|
| Is the domain reachable from my browser right now? | Yes | The fetch either resolves or throws within 5 seconds |
| Did the URL return 404 / 410 / 500? | No | no-cors responses are opaque — the status code is unreadable |
| Is a CORS-blocked site actually alive? | No | A blocked request throws and is indistinguishable from a dead host |
| Does a relative file path exist on disk? | No | Browsers cannot read the local filesystem; relative links are format-checked only |
Cookbook
Real rot-sweep scenarios with the exact report lines they produce, and how to tell a true dead link from a CORS false positive.
A genuinely dead third-party link
An old vendor URL whose domain no longer resolves. The probe throws and the report flags it.
Input: See the original announcement at https://defunct-startup-2019.com/blog/launch. Report: # Link Validation Report Checked 1 external URLs and 0 relative links. > Note: Due to browser CORS restrictions, only DNS/network reachability can be tested, not HTTP status codes. ## External URLs ✗ https://defunct-startup-2019.com/blog/launch (unreachable or CORS-blocked) Verdict: opening it in a tab shows a DNS error -> confirmed dead, replace the link.
A live site that shows ✗ because of CORS
A perfectly healthy page that rejects cross-origin requests. The report cannot distinguish it from a dead host, so manual confirmation is required.
Input: Docs: https://www.some-strict-site.com/guide Report: ## External URLs ✗ https://www.some-strict-site.com/guide (unreachable or CORS-blocked) Verdict: opening it in a tab loads the guide fine -> CORS false positive, leave the link as-is.
Mixed live and dead links in one sweep
A reference list where most links are healthy and one is rotten. The healthy ones show ✓ (subject to CORS), the dead one shows ✗.
Input: - https://example.com/a - https://example.com/b - https://gone-domain-xyz.test/c Report: ## External URLs ✓ https://example.com/a ✓ https://example.com/b ✗ https://gone-domain-xyz.test/c (unreachable or CORS-blocked)
A 404 that still shows ✓
The host is alive and serving, but the specific path returns 404. Because the status code is unreadable in the browser, the link passes the reachability probe. This is the validator's main blind spot.
Input: Guide moved: https://example.com/old-path-that-404s Report: ## External URLs ✓ https://example.com/old-path-that-404s Note: ✓ here only means the server responded; it does NOT mean the page exists. Use a server-side crawler to catch 404s on reachable hosts.
More links than the 50-URL cap
A link roundup with 73 unique URLs. Only the first 50 are probed; the rest are summarised.
Input: 73 unique https:// links in a curated list. Report (tail): ✓ https://example.com/49 ✗ https://example.com/50 (unreachable or CORS-blocked) ... and 23 more To cover all 73, split the page into two with md-splitter and run each.
Edge cases and what actually happens
✗ on a live, CORS-blocked site
False positiveA healthy site that rejects cross-origin requests throws on probe and is reported ✗ (unreachable or CORS-blocked). There is no way to distinguish it from a dead host in the browser. Always open ✗ links in a tab before treating them as rot.
✓ on a reachable host that returns 404
Blind spotThe probe only learns whether the request threw; it cannot read the HTTP status. A live server returning 404, 410, or 500 still produces ✓. For status-code-level link checking, use a server-side crawler — this tool finds reachability rot, not path-level rot.
Probe times out after 5 seconds
TimeoutEach URL gets an AbortController that fires at 5000 ms. A slow endpoint aborts and is reported ✗, even if it would have loaded eventually. The timeout is fixed and cannot be changed.
More than 50 unique external URLs
CappedOnly the first 50 unique URLs are probed; the remainder show as ... and N more and are not tested. Split link-heavy pages with md-splitter so every URL is covered across runs.
Reference-style links never appear
Not checkedLinks written as [text][ref] with a separate [ref]: url definition match neither regex and are skipped. A doc full of reference links can report Checked 0 external URLs. Convert with md-ref-link-converter first.
Transient network failure during a run
UnreachableIf your own connection drops or a DNS hiccup occurs mid-run, healthy URLs can momentarily show ✗. Re-run the validation once your connection is stable before deleting any links.
URLs inside fenced code blocks
ExpectedThe regex scans raw text and does not skip code fences. A sample URL inside a ``` block is extracted and probed like any other. A deliberately fake example URL will correctly show ✗` — that is expected, not a bug.
Rate-limited endpoints
UnreachableA site that aggressively rate-limits may reject or stall the probe, producing a ✗. This is a probing artefact, not necessarily rot. Confirm important links manually.
Relative path that points at a deleted file
Not detectedA [text](./deleted.md) link whose target was removed still passes the format check (✓) because the tool only inspects path shape, never the filesystem. Use your build/dev preview to catch missing local files.
Input exceeds the free tier limit
413 rejectedFree runs cap at 1 MB / 500,000 characters; the character cap is independent of byte size. Oversized input is rejected. Pro raises it to 10 MB / 5,000,000 characters.
Frequently asked questions
Why does the tool only do DNS/reachability checks?
Browser CORS prevents JavaScript from reading HTTP status codes on cross-origin requests. A no-cors fetch returns an opaque response — the validator can only tell whether the request threw. Reachability is the most a client-side tool can establish.
Is a ✗ a confirmed broken link?
No — it is a candidate. A ✗ can mean a truly dead domain, a DNS failure, a timeout, or a perfectly live site that blocks cross-origin requests. Always open the URL in a browser tab before deleting it.
Can a ✓ link still be broken?
Yes. ✓ only means the host responded without the request throwing. A reachable server returning 404 on that exact path still shows ✓. To catch path-level 404s, use a server-side crawler.
How many links does one run check?
Up to 50 unique external URLs are network-tested. Beyond 50 you get a ... and N more line and those are not probed. Relative-path format checks have no cap.
What is the per-URL timeout?
5 seconds. Each probe is wrapped in an AbortController that fires at 5000 ms; a slower endpoint aborts and is reported ✗. The timeout is fixed and not configurable.
How can I get full HTTP-status link checking?
Use a server-side crawler outside the browser — that is the only way to read status codes. This tool is a fast, private, browser-based first pass for reachability-level rot, not a replacement for a CI crawler.
Are duplicate links probed multiple times?
No. The URL list is de-duplicated with a Set before probing, so a link that appears many times in the doc is tested once and listed once.
Why did a known-broken reference link not show up?
Reference-style links ([text][ref]) are not extracted by the regexes. Convert them to inline links with md-ref-link-converter, then re-run so they get probed.
Does it check whether a relative file actually exists?
No. Relative links get a regex format check only — ✓ (safe shape) or ? (risky shape). The browser cannot read your filesystem, so missing local files are not detected here.
Is my document sent to a server?
The Markdown is parsed in your browser and not uploaded. The external-URL probes are outbound requests from your browser to those URLs (required to test reachability), but the doc text stays local.
Can I schedule or automate this?
The tool itself is a one-click browser action with no scheduling. There is a documented public API (GET /api/v1/tools/{slug}) that returns each tool's metadata and option schema and requires an API key; for automated 404 crawling you would still want a dedicated server-side crawler.
What output do I get?
A plain-text report titled # Link Validation Report, downloadable as <name>-link-report.txt. It lists external URLs with ✓/✗ and relative links with ✓/?.
Privacy first
All Markdown processing runs locally in your browser using JavaScript. No file is ever uploaded to JAD Apps servers — only metadata counters are saved for signed-in dashboard stats.