How to validate relative paths in markdown
- Step 1Open the Link Validator — Open md-link-validator. It has no options — relative-path checking is part of the single validation pass, alongside external-URL probing.
- Step 2Drop the doc you are about to migrate — Paste or drop one
.md/.mdx/.markdownfile. Pick the doc whose links you are about to move between repos or directory structures. - Step 3Run and jump to the Relative Links section — After running, skip the External URLs section and read
## Relative Links (format check only). Each unique relative target shows✓(safe shape) or?(suspicious shape). - Step 4Normalise every ? path — A
?usually means a space, a query string, a//prefix, or unusual characters. Decide the canonical form for the destination — for example, slugify a folder with spaces — before you rebase. - Step 5Rebase paths in bulk — Once the shapes are clean, run md-image-path-rewriter to swap the old base path for the new one across the whole doc (regex mode available for complex moves).
- Step 6Verify file existence in your dev environment — The validator confirms shape, not existence. After migrating, build or preview the site so your framework surfaces any path that still points at a missing file.
Relative-path shapes and how the format regex grades them
The regex is ^[./\w-]+(\.\w+)?(#[\w-]+)?$. It allows dots, slashes, word characters, hyphens, an optional extension, and an optional #anchor. Anything else is ?.
| Path example | Result | Why |
|---|---|---|
./api/guide.md | ✓ | Dots, slashes, word chars, hyphens, and an extension — all allowed |
../assets/logo.png | ✓ | Parent-directory .. and an extension match the safe shape |
/blog/post-slug | ✓ | Leading slash, word chars and hyphen are allowed; no extension is fine |
./page.md#setup | ✓ | Optional trailing #anchor is permitted (existence not verified) |
./my docs/page.html | ? | The space is not in the allowed character set |
./page.html?ref=nav | ? | ? and = (query string) are not allowed |
//cdn.example.com/x.png | ? | Protocol-relative // falls into the relative pass and fails the regex |
What this check proves vs. what migration still needs
Shape validation is one step. These are the gaps to cover with other tools and your build.
| Concern | This tool | Where to handle it |
|---|---|---|
| Path shape is well-formed | Yes — ✓/? per the regex | Here |
| Target file exists in the destination | No — filesystem not readable | Your build / dev preview |
| Bulk-rebasing old base path to new | No — read-only report | md-image-path-rewriter |
Image  links specifically | Only if written as [text](path); ![...] image syntax is not matched by the relative-link regex | md-image-path-rewriter handles  and reference images |
Cookbook
Migration-flavoured inputs and the relative-path report they produce, plus the follow-up tool to actually rebase the paths.
Clean repo-relative paths pass
A doc with conventional relative links. All targets match the safe shape, so the report is all green ticks — the migration can proceed once paths are rebased.
Input: See [setup](./setup.md) and [the API](../api/index.md#auth). Report (relative section): ## Relative Links (format check only) ✓ ./setup.md ✓ ../api/index.md#auth
A folder with a space breaks the shape
A legacy folder name contains a space. It fails the regex and is flagged ?. Slugify the folder before migrating so the path is portable.
Input: Old doc: [archived](./old docs/2021/report.md) Report: ## Relative Links (format check only) ? ./old docs/2021/report.md Fix: rename the folder to old-docs, then update with md-image-path-rewriter (find: ./old docs/ replace: ./old-docs/).
Query strings on internal links
A CMS export added tracking query strings to internal links. The ? and = characters fail the safe-shape regex.
Input: [dashboard](./dashboard.html?utm_source=docs) Report: ## Relative Links (format check only) ? ./dashboard.html?utm_source=docs Fix: strip the query string for repo-internal links before migrating.
Protocol-relative asset URL
A //cdn asset link is not http(s), so it lands in the relative pass and fails the regex. Decide whether it should become an absolute https URL (probed) or a true repo path.
Input:  [mirror](//cdn.example.com/file.zip) Report: ## Relative Links (format check only) ? //cdn.example.com/file.zip Note: the  image is NOT matched (image syntax), only the [mirror](...) inline link is. Convert to https:// to have it network-tested.
Validate, then bulk-rebase the whole tree
The end-to-end migration flow: confirm shapes are clean here, then rebase every path in one pass with the rewriter.
Step 1 (this tool) report: ## Relative Links (format check only) ✓ ./images/a.png ✓ ./images/b.png ✓ ./images/c.png Step 2 (md-image-path-rewriter): find: ./images/ replace: https://cdn.newsite.com/docs/images/ -> every image path rebased to the new CDN base.
Edge cases and what actually happens
Path passes shape but file does not exist
Not detectedA [text](./moved-away.md) link keeps its ✓ even after the target file is deleted or moved, because the tool only inspects the path string, never the filesystem. Catch missing files with your build or dev preview after migrating.
Folder or file name contains a space
SuspiciousSpaces are not in the regex's allowed character set, so ./my docs/x.md is flagged ?. URL-encoded %20 is also not in the set and would be flagged. Slugify the name and rebase with md-image-path-rewriter.
Query string on an internal link
SuspiciousCharacters like ?, =, and & are not allowed by the safe-shape regex, so ./page.html?ref=nav is ?. The validator treats these as risky for a repo-internal path; strip the query string or move it to an absolute URL.
Protocol-relative `//host/...`
SuspiciousA //cdn.example.com/x target does not start with http(s), so it enters the relative pass and fails the regex (?). It is never network-tested. Rewrite to an absolute https:// URL if you want it probed instead of format-checked.
Image links written as 
Not checkedThe relative-link regex matches [text](path), not the image form . Pure image links are not extracted by this validator. For image paths specifically, use md-image-path-rewriter, which targets  and reference-style images.
Anchor-only links like (#section)
Format check onlyA same-page anchor [jump](#section) matches the regex (it is # + word chars), so it shows ✓. The validator does NOT confirm that a heading named section exists; verify anchors in a rendered preview.
Windows-style backslash paths
SuspiciousBackslashes are not in the allowed set, so .\docs\x.md is flagged ?. Markdown links should use forward slashes; convert backslashes before migrating.
Reference-style relative links
Not checkedA relative link written as [text][ref] with a [ref]: ./path definition is not matched by the inline regex and is skipped. Convert to inline with md-ref-link-converter before checking.
Encoded characters in the path
SuspiciousPercent-encoding (%20, %2F) and other punctuation outside . / word-char - fall outside the regex and are flagged ?. Decide on the canonical decoded form for the destination tree.
Input exceeds the free tier limit
413 rejectedFree runs cap at 1 MB / 500,000 characters per file; the character cap is separate from byte size. Oversized files are rejected before processing. Pro raises this to 10 MB / 5,000,000 characters.
Frequently asked questions
Does it confirm the relative file actually exists?
No. Browser tools cannot read the local filesystem. Relative paths get a format check against ^[./\w-]+(\.\w+)?(#[\w-]+)?$ only — ✓ means the shape is safe, ? means it is risky. Verify existence in your build or dev preview.
What kinds of paths get flagged with ?
Anything outside the allowed set: spaces, query strings (?, =, &), protocol-relative // prefixes, backslashes, percent-encoding, and other punctuation. The check is intentionally strict so risky migration paths surface early.
Are image links checked too?
Only if written as inline [text](path) links. Pure image syntax  is not matched by the relative-link regex. For image-path migration specifically, use md-image-path-rewriter, which handles  and reference-style images.
Will it work for Astro / Next.js route paths?
Yes for the shape check — a path like /blog/post-slug matches the regex and shows ✓. The tool does not resolve framework routes or confirm the page exists; it only validates the string shape.
Does it accept query strings in paths?
The regex does not allow ? or =, so a path with a query string is flagged ? (suspicious). For repo-internal links you usually want to strip query strings before migrating; for true external URLs, write them as full https:// links so they enter the URL pass instead.
How do I actually rewrite the paths after checking?
This tool is read-only — it reports, it does not edit. Run md-image-path-rewriter to rebase paths in bulk (it supports a plain find/replace and an optional regex mode for complex moves).
Are duplicate paths listed more than once?
No. Relative-link targets are de-duplicated with a Set, so a shared asset linked from several places in the doc appears once in the report.
Does an anchor like ./page.md#setup get fully validated?
Only the shape: the optional #setup suffix is permitted by the regex, so it passes. The tool does not check that a heading named setup exists in page.md. Confirm anchors in a rendered preview.
What about reference-style relative links?
They are not matched by the inline regex and are skipped entirely. Convert them to inline form with md-ref-link-converter first, then re-run the check.
Is my content uploaded?
No. Relative-path checking is pure regex on the text and runs in your browser with no network calls and no upload. (External-URL probing in the same run does make outbound requests, but relative paths never leave your machine.)
Can I check several docs at once?
Not directly — the validator takes one file per run. Merge a migration batch with md-merger first, validate the combined file, then split back if needed.
What output do I get?
A plain-text # Link Validation Report saved as <name>-link-report.txt, with a ## Relative Links (format check only) section listing each unique relative target as ✓ or ?.
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.