The #pdf registry
The typed server module for pdf, renderPdf, the unsafe escape hatch, and the render result.
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 deliberately does not expose the compile-time definition, preview fixtures, 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 & debugging.
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) |
registeredFontFaces | Frozen family/weight/style facts; never source bytes or paths |
Diagnostics deliberately contain no document content, props, or resource URLs, so they are safe to send to an application metrics layer.
Development and production boundary
The development preview keeps its sampleData, named scenarios, and source
file in an internal sidecar. It calls the same public render(props) method as
application code, reads the viewer title from that exact result's frozen
metadata, and parks the completed result for the iframe. There is no separate
preview renderer, second metadata evaluation, or second document render.
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 sidecar and preview routes are also absent.
The production fixture protects this boundary with canary values inside its preview data. Its build test recursively scans the emitted Nitro server artifact and fails if either canary or a preview-only API token is present. A separate bundle check keeps 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 nuxt prepare and on template changes in dev. If
#pdf imports appear untyped right after enabling the module, restart nuxt dev
or run nuxt prepare once.