A documentation set is mostly cross-references. Pages point at each other constantly, and in a set that is still being built those targets move: a slug gets corrected, a folder gets renamed, half a section gets reorganized into two. Leed’s internal links survive all of it, because a link does not store a URL. It stores the page.
Why a link stores a page, not a path
When you link to one of your own pages, Leed writes a reference to that page’s identifier rather than the address it happens to have today:
[Left Navigation Menu](pageid:2d4a8a2c-1f19-4d74-8b7c-b64403b12bf6)The address is resolved during the site build, from wherever the page lives at that moment. Change its slug, move it to a different folder, restructure the whole set — every link to it comes out right on the next publish, with no editing pass over the pages that point at it.
A hand-typed path gets none of that. /docs/documentation-sites/left-navigation-menu/ is a string; nothing connects it to the page, nothing rewrites it when the page moves, and nothing tells you it has gone stale. It simply becomes a 404 that no one notices until a reader reports it. Type a path only for a page that is not yours.
Authoring a link
Select the text you want to turn into a link and open the link control. The picker has three tabs.
Internal Page searches every page in your workspace and stores a reference to the one you pick. This is the tab to use for anything inside your own site, documentation or not.
External URL takes an address and stores it verbatim, opening in a new tab.
Documents lists the document assets in your library and links to the file rather than to a page. See linking to a file instead of a page below.
The picker behaves identically outside a documentation set, and Links and the Link Picker documents it in full — including what it does with link titles, downloads and existing links you re-open. The stored form and how it round-trips through Leed Markdown are covered at Links and Internal Links.
What a reader sees when the target is not published
This is the part that surprises people, so it is worth being exact: a link to a page that is not published does not break. It disappears.
During the build, Leed resolves every page reference against the pages actually being rendered. A reference that matches nothing has its link removed and its text left in place. The reader sees ordinary words in the middle of your sentence — no 404, no error page, no broken-link styling. Nothing tells them, or you, that a link was ever there.
The build does this by swapping the <a> for a <span class="page-removed"> holding the same text. So a pageid: link that resolves stays a normal link with a real URL, and one whose target is missing becomes that span. Search your built site for page-removed to find every one of them.
The other three surfaces that carry page references behave differently from each other:
| Where the link is | What the reader sees | What is preserved | What is lost |
|---|---|---|---|
| In a page body | The link text, as plain unlinked words | The wording and its position in the sentence | The link, silently |
| A page in the left navigation | Nothing — the row is omitted from the sidebar entirely | Nothing | The whole navigation entry |
| A folder in the left navigation | The folder and everything under it, still navigable | The folder label and its children | Nothing — folders never link anyway |
| Previous / next | The control still renders, with its label unlinked | The pager’s position in the reading order | The link |
| Breadcrumbs | The crumb still renders, unlinked | The trail | The link |
The sidebar case is the one that costs you time. An unpublished page is not shown grayed out or marked “coming soon”; its row is simply not there, and a set that is half-published looks like a set with holes in its navigation. That is why Publishing a Documentation Set tells you to publish everything and then judge the navigation, rather than the other way round.
Heading anchors and deep links
Every heading from ## down to ##### gets an anchor id, slugified from the heading’s own text — ## Reserve pages first, then write becomes reserve-pages-first-then-write. A sixth-level heading gets none. Two headings with the same text on one page get -1, -2 and so on appended in document order, so the second “Options” is options-1.
Within a page, link to one with an ordinary fragment:
[what a reader sees](#what-a-reader-sees-when-the-target-is-not-published)How a heading becomes an anchor slug
- The text is lowercased, non-alphanumeric runs collapse to a single hyphen, and leading and trailing hyphens are trimmed.
## Alpha, Bravo and Charlie→alpha-bravo-and-charlie. - Runs of capitals are split:
## Importing an OpenAPI Spec→importing-an-open-api-spec. Check the anchor rather than assuming it, on any heading carrying an acronym. - Inline code counts as heading text, so a token inside backticks is slugified along with the words around it.
- Ids stop at the fifth heading level. A sixth-level heading gets none and cannot be linked to.
- Collisions are resolved by appending
-1,-2in the order the headings appear.
Linking to a section of another page
Add the fragment after the page reference. The build resolves the page and carries the fragment through, so the reader lands on the heading rather than at the top of the page:
[naming your own theme](pageid:92000d4b-eca4-4a89-bfa8-f16da9801a13#naming-your-own-theme)The picker does not offer a list of a page’s headings, so the fragment is typed by hand. The reliable way to get one is from the published page itself: hovering any ##, ### or #### heading reveals a Copy link to section control that puts the full URL, fragment included, on your clipboard — one of the reader-facing controls covered in Documentation Reading Experience.
Two limits worth knowing, neither of them an error condition. A fragment that no longer matches any heading is not a broken link and does not redirect — the reader lands at the top of the correct page and reads from there, which is why a stale fragment is a mild cost rather than a defect. And a query string attached to a page reference survives exactly the way a fragment does, in the order path, query, fragment.
Reserve pages first, then write
You cannot choose a page’s identifier. Creating a page returns one, and that returned value is what links and menu entries reference from then on. So a set of any size is not built page by page — you cannot write page 3’s body until pages 4 through 40 exist to be linked to.
The order that works is: create every page in the set first, with nothing but a title; collect the identifiers; then author the bodies against them. This is exactly why the build-out sequence in Creating a Documentation Set puts page creation before authoring and the menu save after both. It is the same sequence an AI client should follow, and Authoring Pages Over MCP shows the reserve-then-fill calls.
Two things about a reserved page that catch people out, both covered in full at Folders Set Your URLs: the slug comes from the title and any slug you supply at creation is ignored, and a slug that collides with a sibling gets -2 appended. Fix both before anything links to the page, not after.
Verifying the set’s links
Because an unresolved reference is silent, verification is a search rather than a report. After the whole set is published:
- Search the built pages for
page-removed. Every hit is a link whose target is unpublished, deleted, or in another workspace. There should be none. - Walk the left navigation and confirm every page you expect is present. A missing row means an unpublished page, not a menu mistake.
- Open the first and last page of the set and confirm previous/next resolve — those two sit on the seam with the neighboring sections and are where an ordering mistake shows first.
Do this after the whole set is live. Run it against a half-published set and every result is noise.
Linking to a file instead of a page
The picker’s Documents tab links to a document asset rather than a page. The stored form is the asset’s own address — f/<assetId>/<filename> — which your published site serves directly, so a link to a PDF keeps working when the document is replaced with a new version under the same asset. That is a different mechanism from page references and does not participate in any of the moves described above.