How to run a link check before you publish
- Step 1Open the validator as your last pre-publish step — Open md-link-validator once the draft is otherwise final. There is nothing to configure — it is a single-action gate.
- Step 2Drop the near-final draft — Paste or drop the one
.md/.mdx/.markdownfile you are about to publish. Run the sweep on the exact text that will ship, not an earlier copy. - Step 3Run and check the count line — Read
Checked N external URLs and M relative links.— confirm the numbers match the links you expect. A surprisingly lowNusually means some links are reference-style and were not extracted. - Step 4Resolve external ✗ entries — For each
✗, open the URL in a tab. Replace genuinely dead links; leave live-but-CORS-blocked ones (note them in the PR so reviewers know they were checked manually). - Step 5Clean up relative ? entries — Fix any
?relative paths — spaces, query strings, protocol-relative prefixes — so internal links are portable. Confirm the targets render in your site preview. - Step 6Attach the report and publish — Save
<name>-link-report.txt, attach it to the PR, and publish. Re-run on long-lived pages periodically to catch link rot after release.
Pre-publish link-check checklist
What to confirm in each section of the report before you ship.
| Checklist item | Report signal | Pass condition |
|---|---|---|
| Count line matches expectations | Checked N external URLs and M relative links. | N and M roughly match the links you wrote (low N hints at reference-style links) |
| No genuinely dead external links | ✗ <url> entries | Every ✗ confirmed in a tab as either fixed or a CORS false positive |
| No risky relative-path shapes | ? <path> entries | All ? paths normalised (no spaces, query strings, //) |
| Under the 50-URL cap | ... and N more line absent | No more line means every external URL was probed; if present, split the doc |
| Report saved as evidence | <name>-link-report.txt | File attached to the PR or release record |
Two limits to design your checklist around
Plan the QA step knowing these constraints so nothing slips through.
| Limit | Effect | Mitigation |
|---|---|---|
| 50 external URLs probed per run | Link-heavy pages leave some URLs untested (... and N more) | Split with md-splitter and run each part |
| Status codes unreadable (CORS) | A reachable 404 page still shows ✓ | Spot-check key links manually; use a server crawler for status-level QA |
| Reference-style links not extracted | [text][ref] links never appear in the report | Convert to inline with md-ref-link-converter |
| Free tier 1 MB / 500,000 chars | Very large compiled docs are rejected | Upgrade tier or validate per-section |
Cookbook
Release-day scenarios with the report each produces, framed as go / no-go gates for publishing.
Clean release note passes the gate
A finished changelog with a handful of external and internal links. Everything is reachable or shape-valid — green light to publish.
Input (release-notes.md): Upgrade guide: [here](./upgrade.md). Blog: https://example.com/blog. Report (release-notes-link-report.txt): # Link Validation Report Checked 1 external URLs and 1 relative links. > Note: Due to browser CORS restrictions, only DNS/network reachability can be tested, not HTTP status codes. ## External URLs ✓ https://example.com/blog ## Relative Links (format check only) ✓ ./upgrade.md Verdict: GO.
A dead external link blocks the release
The launch post links to a partner page that is now down. The sweep catches it before readers do.
Report: ## External URLs ✓ https://example.com/feature ✗ https://partner-old-domain.test/case-study (unreachable or CORS-blocked) Verdict: NO-GO until the partner link is verified in a tab and fixed or replaced.
A relative path with a space slips into a draft
A copy-pasted internal link points at a folder with a space. The shape check flags it so it gets fixed before publish.
Report: ## Relative Links (format check only) ? ./press kit/2026/onepager.pdf Verdict: NO-GO. Rename to press-kit and update the link before shipping.
Link-heavy resource page exceeds the cap
A curated roundup has 60 external links. The report probes 50 and notes the rest, so the checklist requires a split to fully cover it.
Report (tail): ✓ https://example.com/49 ✓ https://example.com/50 ... and 10 more Verdict: HOLD. Split with md-splitter, validate both halves, then publish.
Report attached to the PR as evidence
Once clean, the report file becomes part of the release record so reviewers can see the link check ran on the shipped text.
PR description: Pre-publish link check: PASS Attached: release-notes-link-report.txt - 12 external URLs: all reachable (2 CORS-blocked, verified manually) - 5 relative links: all shape-valid
Edge cases and what actually happens
Reachable page returning 404 shows ✓
Blind spotBrowser CORS makes the HTTP status unreadable, so a live server returning 404 on the linked path still passes the probe as ✓. Spot-check critical links manually, and use a server-side crawler when status-level QA is mandatory for the release.
Live but CORS-blocked link shows ✗
False positiveA healthy site that blocks cross-origin requests is reported ✗ (unreachable or CORS-blocked). Do not block the release on it blindly — open it in a tab to confirm, and note in the PR that it was verified manually.
More than 50 external URLs in the doc
CappedOnly the first 50 unique external URLs are probed; the rest show ... and N more. For a thorough release gate on a link-heavy page, split it with md-splitter and run each section so nothing ships unchecked.
Reference-style links not in the report
Not checked[text][ref] links plus their [ref]: url definitions are not extracted, so they bypass the gate entirely. Convert them to inline with md-ref-link-converter before the final sweep, or they will publish unvalidated.
Probe times out at 5 seconds
TimeoutEach URL aborts at 5000 ms. A slow but healthy endpoint can show ✗ on a single run. Re-run before declaring a no-go, and confirm in a tab if a key link keeps timing out.
Anchor links to renamed headings
Format check onlyA [jump](#old-heading) passes the format check even after you rename the heading, because the tool never resolves anchors against your headings. Verify in-page anchors in the rendered preview as part of the checklist.
Validating an early draft, not the final text
Stale checkRunning the sweep on an earlier copy and then editing links afterwards defeats the gate. Always validate the exact text that will ship, ideally as the last action before merge/publish.
Transient connectivity during the sweep
UnreachableA dropped connection or DNS hiccup mid-run can mark healthy URLs ✗. Treat a sudden cluster of ✗ as suspect, re-run on a stable connection before acting.
Compiled docs over the size limit
413 rejectedA single very large compiled page can exceed the Free cap of 1 MB / 500,000 characters and be rejected. Validate per-section, or upgrade to Pro (10 MB / 5,000,000 characters) for the bigger build outputs.
URLs inside example code blocks
ExpectedThe regex scans raw text and includes fenced code. A sample URL in a code block is probed like any real link, so a deliberately fake example URL will show ✗. That is expected — exclude such samples from your go/no-go judgement.
Frequently asked questions
Why make this the last step before publishing?
Links change up to the final edit, and a broken link in a launch post is the most visible kind of error. Running the sweep on the exact text you ship — as the last action — gives the strongest guarantee the published links were checked.
Can a passing report still hide a broken link?
Yes, in two ways: a reachable host returning 404 shows ✓ (status codes are unreadable in the browser), and reference-style links are not checked at all. Spot-check key links manually and convert reference links to inline before the sweep.
Should I block the release on every ✗?
No — confirm each ✗ first. Many are live sites that block cross-origin requests. Open them in a tab; block only on genuinely dead links, and note CORS false positives in the PR.
How many external links does one run cover?
Up to 50 unique external URLs are probed; beyond that you get ... and N more and those are untested. For link-heavy pages, split with md-splitter so the whole doc is covered before release.
Can I add this to CI?
The tool is a one-click browser action; there is no built-in CI runner. A documented public API (GET /api/v1/tools/{slug}, API-key protected) returns tool metadata and schema, but for automated status-code link checking in CI you would use a dedicated server-side crawler.
What evidence can I attach to the PR?
Download the plain-text report (<name>-link-report.txt) and attach it, or paste its contents into the PR description. It records the counts and the ✓/✗/? results for the shipped text.
Does it validate in-page anchors?
No. Anchors like #section pass the relative format check but are never resolved against your headings. Confirm anchors in the rendered preview as a separate checklist item.
How often should I re-run after publishing?
For long-lived pages, periodically — link rot is continuous. The pre-publish run is your release gate; periodic re-runs catch external links that die after launch.
Are duplicate links re-probed?
No. Both lists are de-duplicated with a Set, so each unique link is checked once. The count line reflects unique links, which keeps the run fast on repetitive docs.
Is the unpublished draft uploaded anywhere?
The Markdown is parsed in your browser and not uploaded. External-URL probes are outbound requests from your browser to those URLs (needed to test reachability), but the embargoed text stays local.
What is the per-URL timeout?
5 seconds. Each probe aborts at 5000 ms via an AbortController. A slow endpoint may report ✗ on one run; re-run before declaring a no-go.
What if my doc is too big?
The Free tier caps a run at 1 MB / 500,000 characters (the character cap is separate from byte size). Validate per-section or upgrade to Pro (10 MB / 5,000,000 characters) for large compiled outputs.
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.