Registry and render result
The typed #pdf server module, runtime dispatch, and completed render conversions.
Nuxt generates an inspectable #pdf module from the discovered templates. It is
a server-only module for Nitro code and is typed from each SFC's props. Do not
import it from a client component or other browser code.
import { pdf, renderPdf, pdfTemplateKeys } from '#pdf'pdf is an object keyed by template name. Property access infers each SFC's
props. Every value has exactly three public members:
interface PdfTemplate<Props extends object> {
readonly key: string
resolveMetadata(props: Props): {
title?: string
filename?: string
language?: string
}
render(props: Props): Promise<PdfRenderResult>
}The handle does not expose the compile-time definition, preview data, source file, or a second preview render method.
await pdf.invoice.render({ invoice })
await pdf.report.render({ report })Nested template paths keep their slash keys (for example
pdf['reports/monthly']).
renderPdf
renderPdf(name, props) is the functional form. A literal template name
infers its props:
await renderPdf('invoice', { invoice })A runtime string cannot be type-checked, so it requires the explicit unknown-props escape hatch:
await renderPdf(templateName, untypedProps, { unsafe: true }){ unsafe: true } does not validate the props; it only makes the loss of
static typing visible at the call site. An unknown template name still fails with
PDF_TEMPLATE_NOT_FOUND.pdfTemplateKeys is the array of discovered template names.
getPdfTemplate(name) returns that typed three-member handle for a literal name,
or undefined for an unknown name. Reach for it when you need resolved
definePdf metadata without rendering:
import { getPdfTemplate } from '#pdf'
const template = getPdfTemplate('invoice')
const { title, filename } = template.resolveMetadata(props)resolveMetadata() evaluates the title and filename declared by
definePdf, and returns its language. It does not mount the component, so an
authored PdfDocument fallback is not part of this result.
NuxtPdfError and PDF_ERROR_CODES are also re-exported from #pdf, so
server code can branch on render failures without extra imports. See
Errors and limits.
The render result
Both pdf.name.render(props) and renderPdf(...) resolve to a
PdfRenderResult. The document is rendered once; every conversion reuses that
completed, immutable byte buffer:
const result = await pdf.invoice.render({ invoice })
result.metadata // frozen title, filename, and language for this render
await result.toUint8Array() // Promise<Uint8Array>
await result.toBuffer() // Promise<Buffer>
await result.response() // Promise<Response>result.metadata is the frozen title, filename, and language resolved for
this render: definePdf values win when present, otherwise authored
PdfDocument fallbacks. title and language describe PDF Info fields;
filename is response/download metadata and is not written into PDF Info.
result.diagnostics is one frozen object shared with the development preview:
| Field | Meaning |
|---|---|
durationMs | Wall time for the whole public render, including metadata evaluation |
byteLength | Exact output byte count |
pageCount | Number of rendered pages |
passes | Layout passes used (1 normally, more for a TOC) |
layoutWarnings | Frozen, structured warnings for layout that rendered but needs attention |
registeredFontFaces | Frozen family/weight/style facts; never source bytes or paths |
Diagnostics contain no document content, props, or resource URLs. An application can send them to its metrics layer.
layoutWarnings is empty for a healthy layout. Nuxt PDF currently reports
PDF_UNBREAKABLE_NODE_OVERFLOW when a non-fixed node cannot wrap and is taller
than the page's usable content height. The warning identifies the page and
primitive and includes both heights in points, without copying text or ids from
the document. Allow the node to wrap or make it small enough to fit on one page.
Development and production boundary
The development preview keeps sampleData, named scenarios, and the relative
source file outside the public template handle. It calls the same public
render(props) method as application code and displays that completed result.
There is no separate preview renderer or second document render.
Editing a template rebuilds its registry and refreshes the document with its diagnostics. The preview keeps the selected scenario.
In a production build the SFC transform reconstructs the render definition from
only title, filename, language, and maxPasses. sampleData and
scenarios are structurally omitted before Nitro bundles the template; they are
not public handle properties and are not merely hidden behind runtime checks.
The development preview data and routes are also absent. Packed-consumer checks
verify this boundary and keep the renderer and template SFCs out of the client
output.
Metadata is hoisted: inline values and imports are supported, while references
to bindings declared locally in <script setup> are compile errors. Imported
preview-data modules must be side-effect-free so their unused production imports
can be eliminated.
response(init)
response() returns a Response with content-type: application/pdf, the
exact content-length, and a sanitized, encoded-length-bounded
content-disposition filename:
await result.response({
disposition: 'inline', // 'attachment' (default) | 'inline'
filename: 'invoice.pdf', // sanitized before writing the header
headers: { 'cache-control': 'no-store' },
})| Field | Type | Description |
|---|---|---|
disposition | 'attachment' | 'inline' | content-disposition type; defaults to attachment |
filename | string | Download filename; sanitized before use; defaults to template metadata or document.pdf |
headers | HeadersInit | Extra response headers merged in |
status, statusText | number / string | Standard ResponseInit fields |
Caller-provided content-type, content-length, and content-disposition
headers are replaced with the truthful values from the completed result.
Regenerating the registry
The registry is written during module setup and regenerated whenever templates
change in development. If #pdf imports appear untyped in your editor, run
nuxt prepare once to refresh the generated types.