Links, bookmarks and metadata
<Document title="Annual report" author="ACME" lang="en-US">
<Page>
<Text href="#results" className="text-blue-700 underline">Jump to results</Text>
<Text href="https://example.com">Website</Text>
<Text>
Read the <Link href="https://example.com/terms" className="underline">terms</Link>.
</Text>
<Text id="results" bookmark="Results" className="text-xl font-bold">Results</Text>
<Text bookmark={{ title: 'Revenue', level: 2 }}>Revenue</Text>
</Page>
</Document>
href on a Text or View makes the whole element a link, and <Link> does the same as a
component: inside a Text it is a linked run, elsewhere a box like a View. A link takes the
style it is given and adds no colour or underline of its own. href="#id" jumps to the element
with that id.
Bookmarks become the PDF outline in the viewer's sidebar.
Document writes title, author, subject, keywords, creator (the application that
made the file) and producer (the software that wrote the PDF, by default Inkoxide <version>)
into the PDF's properties. creationDate (an RFC 3339 timestamp, "2026-10-12T09:30:00Z") dates
it, as the creationDate option does; without either, the same input gives byte-identical output.
pageMode and pageLayout say how the viewer opens the PDF:
<Document pageMode="useThumbs" pageLayout="twoPageRight">…</Document>
pageMode is 'useNone' (no side panel), 'useOutlines' (the bookmarks), 'useThumbs' (page
thumbnails) or 'fullScreen'. The default shows the bookmarks when there are any. pageLayout
is 'singlePage', 'oneColumn' (continuous scrolling), 'twoColumnLeft' or 'twoColumnRight'
(two scrolling columns, odd pages on the left or right), or 'twoPageLeft' or 'twoPageRight'
(two pages at a time); without it, the viewer uses its own setting.
fallbackFonts lists the fonts tried, in order, for characters a run's font lacks. The default is
['NotoSans-Regular']. The entries are font ids: the bundled Inter-Regular, Inter-Italic,
Inter-Bold, InterDisplay-Bold, NotoSans-Regular and RobotoMono-Regular, or a face added
with Font.register, whose id is its family with spaces as hyphens, its weight, and -italic for
an italic face:
Font.register({ family: 'Noto Sans Arabic', src: './fonts/NotoSansArabic-Regular.ttf' });
<Document fallbackFonts={['NotoSans-Regular', 'Noto-Sans-Arabic-400']}>…</Document>
An id that is not registered is skipped, and fallbackFonts={[]} turns the fallback off.
PDF/A
conformance makes the file PDF/A, for archiving, and so does the pdfA render option, which
wins over it:
<Document title="Invoice 2026-0815" conformance="PDF/A-2b">…</Document>
conformance | pdfA | Writes |
|---|---|---|
'PDF/A-2b' or 'PDF/A-2' | true or '2b' | PDF/A-2b |
'PDF/A-3b' or 'PDF/A-3' | '3b' | PDF/A-3b, as 2b; the level allows embedded files, but Inkoxide cannot attach any yet |
'PDF/A-1b' or 'PDF/A-1' | '1b' | PDF/A-1b, the oldest level, for archives that require it |
PDF/A-1b has no transparency, so a render fails with ENGINE_REJECTED (engineCode
PDFA_CONFLICT) and the component at fault for a colour with alpha below 1 (a transparent
background is fine: it draws nothing), an opacity between 0 and 1, an image with an alpha
channel such as most PNG logos, an SVG drawn with opacity, a mask or a blend mode, or debug.
PDF/A-2b and 3b allow all of these. The a and u levels ('PDF/A-2a', 'PDF/A-3u') need tagged
PDF, which is not supported yet: they fail with TEMPLATE_ERROR.
conformance is not part of the document tree: to render a stored result.document again at
the same level, pass pdfA to renderDocument.
Passwords and permissions
userPassword encrypts the PDF (AES-256, which Acrobat X and later, pdf.js, Chrome and Preview
open): the file opens only with that password. ownerPassword lifts permissions, what a
reader without it may do:
<Document
userPassword="open-sesame"
ownerPassword={process.env.PDF_OWNER_PASSWORD}
permissions={{ printing: 'highResolution', copying: false }}
>
…
</Document>
| Permission | Default | Allows |
|---|---|---|
printing | not allowed | 'lowResolution' or 'highResolution' printing |
modifying | false | changes other than annotations and form fields |
copying | false | copying text and images |
annotating | false | adding annotations and filling in fields |
fillingForms | false | filling in fields |
contentAccessibility | true | extracting text for screen readers |
documentAssembly | false | inserting, rotating and deleting pages |
- Passwords are printable ASCII, at most 127 bytes. With only
ownerPassword, anyone opens the file and the permissions apply; with onlyuserPassword, it is also the owner password, sopermissionsneed anownerPasswordother thanuserPassword. - The
encryptionrender option ({ userPassword, ownerPassword, permissions }) replaces the three props as a whole, for passwords that come from the request rather than the template. - Encryption and PDF/A do not go together: PDF/A forbids encryption.
- A bad value fails with
ENGINE_REJECTEDat its path (options.encryption.userPassword, …). No error shows a password, and the passwords are not part ofresult.documentor of the tree's JSON. - Readers enforce the permissions; the file cannot. Encryption hides the text and images, not the page count or sizes. Field values are hidden, but not which checkboxes are checked, nor the positions of the chosen radio button and list box options.
- The same document and passwords give the same bytes, as unencrypted output does.
Synced from packages/render/GUIDE.md in the inkoxide repository.