There is one link button, Mod-k, and one picker behind it — and the same picker appears everywhere in the CMS that takes a link: the page editor, the menu builder and the CTA editor all open the same three tabs. Learn it once.
The picker is also doing something on your behalf that is easy to miss. Paste a URL from your own site into the External tab and, the moment you click away, it becomes an internal page reference instead — a link that follows the page when you rename it rather than breaking. That conversion, and the cases where it does not happen, are most of this page.
The link picker is free on every plan. One action inside it — inserting a document’s extracted text — is not; it is flagged where it appears.
What the picker does before you type
Open the picker with a bare cursor sitting inside a word and it snaps the selection out to the whole word before showing you anything. Highlight most of a phrase and it expands outward to the word boundaries. This is deliberate: people routinely miss a character when highlighting and do not notice until the published page has a stray letter outside the link. Bold, italic and the other marks behave the same way.
The Link Text field is then pre-filled from that snapped range. Leave it alone and the picker only adds the link mark to the existing words — it does not rewrite them, which matters in Suggesting mode, where a pure mark change is recorded as Add format: link rather than as a delete-and-retype.
If the cursor is already in a link — anywhere inside it, or at either edge — the picker opens on that whole link. The tab that matches its href is selected with the destination filled in: Internal Page with the page selected for a pageid: href, Documents with the document selected for a document link, External URL with the address for anything else. Link Text is filled with the complete link text, every word of it, not just the word under the cursor. Clicking into a link to get there leaves the cursor on the character you clicked.
Applying an edit rewrites the whole link: change the destination and the document holds one link, over the original text, with the new destination — never part of it left behind on the old one. An edit also keeps what the picker does not show, so changing only the Link Text of an external link leaves its new-tab setting in place, along with its rel, download, class, role, ARIA labels and asset reference.
Opening the picker on an existing link also adds a Remove link button beside Cancel and Apply. It appears exactly when the picker has found a link, so it never sits beside an empty destination. It unlinks the whole link — even when your cursor sits at one edge of it or your selection covers only part of it — and keeps the words exactly as they are. In Suggesting mode it is recorded as Remove format: link, so rejecting it puts the link back. The button does not appear while you are creating a new link, because there is nothing yet to remove.
You can check a link without opening the picker at all: hover it. An internal link shows the title of the page it points to, and an external link shows its address.
| Tab | What you pick | Stored href | Default target | Gate |
|---|---|---|---|---|
| Internal Page | One of your own published pages, from a searchable list | pageid:<pageId> | Same tab | None |
| External URL | A web address (http:// or https://) or a mailto: link, typed or pasted | The URL as typed | _blank — a new tab | None |
| Documents | An uploaded document | f/<assetId>/<filename>, marked as a download | Same tab | Inserting the document’s extracted text is Starter and up |
Internal Page
The Internal Page tab is a combobox over your pages. Type and it filters on title, falling back to the path for pages without one; each row shows the title with its full path underneath.
What it stores is not a path. It stores pageid:<pageId> — a reference to the page itself. At build time every one of those references is resolved against the page’s current URL, so:
- Change the page’s slug, move it into a different folder, or restructure the whole section, and the link keeps working. Nothing to find and fix.
- The link can never rot into a path that no longer exists, because there is no path in it to go stale.
That is why URL Paths and Slugs can describe URLs as something you are free to change: internal links do not depend on them.
In the page editor, as in the menu builder, the list also includes pages that have never been published, so you can link forward to a draft — and an existing link to one opens with that page selected. Publish both pages together: until the target is published, the link renders as plain text (see What a reader sees when the target is gone). The CTA editor lists published pages only.
External URL
The External URL tab is a single field. It takes a full web address starting with http:// or https://, a mailto: link, a path beginning with /, or a #heading anchor; an address typed without a scheme, such as example.com/pricing, gets https:// added. Anything else — tel:, javascript: or any other scheme — shows an error under the field and Apply stays disabled. What it accepts is stored verbatim and opens in a new tab — unless it points inside your own site. A #heading anchor or a path beginning with / never opens in a new tab on the published page, because the new-tab setting is dropped from every internal link when the page is written out as Leed Markdown. The one exception is a document’s /f/ download link, which keeps its new tab.
A link whose address uses javascript:, vbscript:, data: or file: can never be created, however it is disguised — mixed case, spaces or tabs inside the scheme, or character references such as :. The same check applies outside the picker: pasting such a link into the editor, or importing Markdown that holds one, keeps the link text and drops the link. A page body stored with one before this check existed is not rewritten, but the editor shows that link without its address, and the published site renders its text with no link.
Why your own URL becomes a page link
When you leave that field — click elsewhere, tab away, press Apply — the picker sends what you typed to Leed and asks whether it resolves to one of your pages. If it does, the field is rewritten to pageid:…, the matching page is selected, and you are switched to the Internal Page tab. You will see the tab flip; that is the conversion happening.
The rule is narrow and worth knowing exactly:
flowchart TD
TYPE["What you typed, trimmed"] --> EMPTY{"Empty?"}
EMPTY -->|yes| KEEP["Left exactly as typed —<br/>stays an external link"]
EMPTY -->|no| PROTO{"Starts with // ?"}
PROTO -->|"yes — protocol-relative,<br/>another host"| KEEP
PROTO -->|no| REL{"Starts with / ?"}
REL -->|yes| LOOK["Look the path up"]
REL -->|no| URL{"Parses as an absolute URL?"}
URL -->|no| KEEP
URL -->|yes| SCHEME{"http or https?"}
SCHEME -->|"no — mailto:"| KEEP
SCHEME -->|yes| HOST{"Host equals your<br/>public domain?"}
HOST -->|no| KEEP
HOST -->|yes| STRIP["Drop the origin,<br/>keep the path"] --> LOOK
LOOK --> HIT{"A page or redirect<br/>matches that path?"}
HIT -->|no| SLASH{"Retry with a<br/>trailing slash"}
SLASH -->|"still no match"| KEEP
SLASH -->|match| WIN
HIT -->|yes| WIN["Rewritten to a pageid: reference<br/>and the tab flips to Internal Page"]
Two details the diagram earns: the lookup matches redirects as well as current URLs, so pasting an old address you kept as an alias still finds the right page; and the trailing-slash retry means /docs/pricing and /docs/pricing/ both resolve.
What does not convert
The host comparison is against your workspace’s public domain and nothing else. Everything below that is not converted or refused is treated as an external link and stored as typed:
| What you paste | Result | Why |
|---|---|---|
/docs/pricing/ or /docs/pricing | Converted to pageid: | A relative path is always looked up |
https://<your public domain>/blog/hello/ | Converted to pageid: | The host matches your public domain |
| A URL on your preview domain | Stays external | Only the public domain is compared |
A *.workers.dev URL for your site | Stays external | Same reason |
| A path for a page that has never been published | Left as a literal path | There is no live URL to match yet, so the link is fixed to that path |
//example.com/thing | Stays external | Protocol-relative — it starts with / but addresses another host |
mailto: | Stays external | Only http: and https: are considered |
tel:, javascript: or any other scheme | Refused — Apply stays disabled | The field takes only web addresses and mailto: links |
| Any other site’s URL | Stays external | As intended |
Documents
The Documents tab lists every document asset in your library by name and extension. Choosing one stores a download link to the file:
pageid:db540641-1361-4474-8590-c95936b5318e
f/3qa11wy1/product-spec.pdfThe first is what an internal page link looks like; the second is what a document link looks like. Documents are uploaded like any other asset — see Video, Audio and Document Assets.
Below the list, with a document selected, sits Insert extracted text. It drops the document’s extracted text into the page as editor blocks, below the paragraph you are in — useful when a PDF holds content you want as real, searchable page content rather than a download.
What a reader sees when the target is gone
A pageid: link is resolved when the site builds. If the page it points at is not in the build — unpublished, deleted, excluded — the link does not 404. The build strips the anchor and leaves the words behind in a plain <span>: the sentence still reads correctly, the text is no longer clickable, and nothing on the page announces that anything happened.
That is a deliberate trade — a silently plain phrase is better for a reader than a link into nothing — but it means a broken internal link is invisible unless you look. If you publish a page that links forward to one you have not published yet, publish both.
The reader-facing half of this, including how it behaves across a documentation set, is on Linking Between Docs Pages.
Linking to a heading
Same-page links work with ordinary markdown: [label](#anchor-slug). Anchors are generated automatically for headings at levels 1 through 5 from the heading’s text, lower-cased and dash-separated. In practice you will only ever use levels 2 through 5, because your page’s H1 comes from its title rather than from the body. ###### gets no anchor at all — if you need to link to something, do not put it at level six.
Two headings with the same text get de-duplicated with a numeric suffix (-1, -2), which is a good reason to keep headings on one page distinct.
Linking to a heading on another page
A fragment on an internal link works too: [label](pageid:<pageId>#heading-slug). The build resolves the page id to its current URL and keeps the fragment (and any query string) on the end, so the reader lands on the section rather than the top of the page.
The syntax for all of this, outside the picker, is on Links and Internal Links.
Keeping links honest
Internal links look after themselves. The two things that need attention are the links that point outward and the URLs that used to point in.
External links are checked automatically once a day and the failures are collected for you — Link Health shows what is broken and where it is linked from. And when you do change a page’s URL, add the old path as a redirect so anything outside your control — a bookmark, a search result, someone else’s blog post — still arrives; Aliases and Redirects covers that, and it is also what makes the picker’s path lookup match an old address.
The Link button’s shortcut and its neighbors on the toolbar are on Editor Toolbar Reference. The same picker builds your navigation, over on Building and Editing a Menu.