Skip to content

Limitations

Known limitations of the current release. If one affects you, please check the issue tracker and open a new issue if it is not already reported.

Complete inline [text](../path) links are matched, including titled links ([text](../x "title")) and angle-bracket destinations ([text](<../x>), which may contain spaces); the title and fragment are preserved, and spaces are percent-encoded in the resulting URL. A bare (non-angle-bracket) destination ends at the first space, so a link to a path that contains spaces such as [text](../wide img.png) is not matched; wrap the destination in angle brackets — [text](<../wide img.png>) — for it to be rewritten. Reference-style definitions ([ref]: ../path, optional title) are rewritten the same way; usages such as [text][ref] pick up the forge URL when the markdown processor resolves the reference. Inline and reference labels may contain nested ([a [b]]) or backslash-escaped ([a\]b]) ] characters.

A complete inline [text](../path) link wrapped in literal square brackets — for example [note: see [config](../config.md)] — is left unchanged. The scanner matches the outer balanced [...] first and, finding no ( after it, treats the whole span as text, so the inner link is not rewritten even though CommonMark renders it as a link. This is uncommon and fails safe (the inner link still works as written); a fully correct fix needs CommonMark delimiter-stack parsing.

Reference definitions must fit on a single line. Multi-line definitions (for example a title wrapped to the next line) are not matched and are left unchanged.

Rewriting is text-level over the page markdown, but fenced code blocks (backtick or tilde fences) and inline code spans are detected and skipped, so a literal ](../path) or [ref]: ../path shown as an example is left unchanged.

Indented code blocks (four-space or tab-indented paragraphs) are not skipped. A complete inline [text](../path) link inside an indented block will be rewritten; lonely ](../path) suffixes in prose are not matched. Use fenced code blocks for examples that must stay literal.

Raw HTML links (<a href="../...">) are not rewritten; only markdown inline and reference-style ../ links are. Rewriting is text-level and does not parse raw HTML block context: a complete [text](../path) link or [ref]: ../path definition written inside a raw HTML block is rewritten like any other link. Use a fenced or inline code span to keep an example literal.

../ targets that include a ?query string are treated as a literal path (the query is not split off), so the resolved path usually does not exist on disk. Such targets are therefore reported as missing — by default a missing-target warning is emitted, which fails mkdocs build --strict unless warn_on_missing: false is set.

HTML comments are not treated as code. A complete [text](../path) link or [ref]: ../path definition inside <!-- ... --> is rewritten like any other link. Use a fenced or inline code span to keep an example literal.

Images are intentionally not rewritten. Inline images (![alt](../path)), including alt text with nested or escaped ] characters, and image reference definitions (![alt][ref] with [ref]: ../path) are left unchanged because forge blob/tree URLs are HTML pages, not raw image assets — rewriting them would break <img> rendering in the built site. If a reference label is used by both a normal link and an image, the definition is skipped so the image keeps working (the normal link usage is left unrevised).

MkDocs virtual pages (generated content with no markdown file on disk) are left unchanged — the plugin needs page.file.abs_src_path to resolve ../ paths from the page location.

Forge detection

URLs are built for GitHub, GitLab, Bitbucket, Gitea/Forgejo, and Azure DevOps. Public hosts and common self-hosted patterns are autodetected; an instance on an unrelated custom domain needs an explicit forge setting, otherwise its links are left unchanged. See Forges (including unsupported forges).

Line anchors

Canonical line fragments in links (#L10, #L10-L20) are translated to each forge's syntax when links are rewritten (GitHub/Gitea keep #L form, GitLab uses #L10-20, Bitbucket uses #lines-10:20). Non-line fragments (#section) pass through unchanged. Azure DevOps view URLs do not support hash-based line anchors; line fragments are omitted for that forge.

Every rewritten ../ link uses the same git ref for the build, from pin and branch: a branch or tag name when pin: branch, the current commit SHA when pin: commit, or an exact tag at HEAD when pin: tag. When pin: commit or pin: tag cannot be resolved (git unavailable, not a repository, or HEAD not on an exact tag), the plugin falls back to the resolved branch and emits a warning at build time. There is no per-link ref override.

Hardcoded tag names

You can set an explicit tag name with pin: branch and branch: v1.2.3 when you are not building from that tag checkout. That always treats the value as a branch ref in URL building — the plugin does not infer tag kind from the string.

On GitHub, GitLab, and Bitbucket Cloud, branch and tag names often share the same URL path shape, so this recipe may still work. On Gitea/Forgejo/Codeberg and Azure DevOps, branch and tag URLs differ (/src/branch/… vs /src/tag/…, or version=GB… vs version=GT…), so hardcoded tag names via branch: produce wrong links on those forges.

For release docs on Gitea or Azure, check out the release tag and use pin: tag. For a single historical link, use a full forge blob URL in markdown (it passes through unchanged). See Pin modes and URL shape.

Branch names from edit_uri

When plugin branch and extra.git_branch are unset, the branch name is parsed from edit_uri by taking the first path segment after edit/ or blob/. Branch names that contain / (for example feature/my-branch in edit/feature/my-branch/docs/) are truncated to the first segment only. This truncation is silent — no build-time warning is emitted, because a multi-segment edit_uri is indistinguishable from a nested docs directory (for example edit/main/packages/app/docs/) without knowing the repository layout. Set branch or extra.git_branch explicitly for slash-containing branch names so forge URLs use the full branch.

Forge URLs use the lexical path written in the markdown (the symlink name), not the symlink target path, while still validating that the resolved target exists inside the repository.

To point at a specific commit or tag for one link — for example code as it existed in an older release — use a full forge blob URL in the markdown. Absolute URLs are left unchanged by the plugin and work in the built site as written.

Existing targets only

A complete inline [text](../path) link or [ref]: ../path definition is only rewritten when its target resolves to a file or directory that exists in the working tree at build time inside the repository. The plugin does not run git to check whether a path existed at the commit, tag, or branch embedded in the forge URL; pin: commit and pin: tag only set that ref in the link (Gitea/Codeberg use /src/tag/… for tags; Azure DevOps uses version=GT…). Build from the checkout you want reflected (for example a release tag) so on-disk paths match the URLs you publish. Directory targets rewrite to forge tree URLs with or without a trailing slash in the markdown (../scripts and ../scripts/ are equivalent when scripts is a directory). Missing targets are left unchanged; by default a warning is emitted (which fails mkdocs build --strict). Set warn_on_missing: false to silence it. Targets that resolve outside the repository are never reported.