Markdown internal links
There is no special syntax for an internal link. It is an ordinary link whose target is a heading in the same document, a file next to it, or a heading in that file. What makes it fragile is that nobody writes the anchor: the renderer derives it from your heading text.
See the installation steps below.
Installation steps
The anchor is the heading, lowercased, with punctuation dropped and spaces turned into dashes.
See the contributing guide and its pull request rules.
Renderers that allow HTML honour the id. Ones that strip HTML do not, so this is a repository trick rather than a portable one.
The anchor is derived, which is why it breaks
You do not choose an anchor. The renderer takes the heading text, lowercases it, drops the punctuation, turns each space into a dash, and that is the anchor. Reword the heading and every link to it stops working, silently: the page still loads and the browser simply does not scroll.
Two headings with the same text produce the same anchor, so renderers number the second one. #setup and #setup-1 are the same words in the same document, and which one is which depends on the order they appear in.
A link between files is resolved by whatever is showing them
[guide](docs/guide.md) works on GitHub because GitHub is showing you a directory. It breaks in a documentation site that turned docs/guide.md into /guide/, and it breaks in any viewer that was handed one file rather than a folder. This is the same problem relative images have, and it has the same answer: an absolute URL where the document has to travel, relative paths where it does not.
What to do about it
Check them. The checker on this site reports a link to an anchor that nothing in the document produces, and the repository checker does it across every markdown file in a repo. Neither can tell you whether a link to another file resolves, because that depends on where the file ends up.
What usually goes wrong
- Reword a heading and every link to it breaks, without any error. The page loads and nothing scrolls.
- Emoji, code spans and links inside a heading all change the anchor. What you see is not what the anchor is made of.
- A second heading with the same text gets a numbered anchor, so
#setup-1may be the one you meant. - A link to a file that has moved cannot be detected by any checker, because nothing in the document says where the file was supposed to be.
Where it behaves differently
It renders as written on GitHub, GitLab and VS Code preview. Everywhere else in this table it does something else, and that is worth reading before you paste.
| Platform | Behaviour |
|---|---|
| Obsidian | Also [[Note#Heading]], which links to a heading in another note. |
| Notion | Not supported. The syntax arrives as its own punctuation. |
| Discord | Not supported. The syntax arrives as its own punctuation. |
| Slack | Not supported. The syntax arrives as its own punctuation. |
Questions
How do I find the anchor for a heading?
On GitHub, hover the heading and copy the link that appears. Otherwise use the contents generator on this site: it produces the anchors GitHub produces, for the whole document at once.
Can I set my own anchor?
In a renderer that allows HTML, yes: an empty element with an id, just before the heading. It is not portable, so it is worth it for a heading you expect to reword and not much else.
Do these anchors work outside GitHub?
GitLab, Bitbucket and most static site generators use the same slug. A few prefix every id for safety, and then links need the prefix too. This site's exported HTML does exactly that, and says so on the contents generator's page.
For the .md file that arrives in Mail
Not released yetA markdown file on a phone is the worst case: no editor, no preview, and a share sheet full of apps that want to import it into a library. The iPhone and iPad app opens it where it landed, renders it, and lets you edit and save it back.
For iPhone and iPad. It is not in the App Store, so there is nothing to download from this page yet.Digital Sandbox, who are building it →