Skip to main content

Quickstart

From an empty pdfs/ directory to a rendered invoice and a live preview in about ten minutes.

This walks from a fresh module install to a rendered invoice, a production-style server route, and the development preview. It assumes you have already installed the module.

Create a template

PDF templates live in pdfs/**/*.vue at the project root. Create pdfs/invoice.vue:

pdfs/invoice.vue
<script setup lang="ts">
type InvoiceProps = {
  invoice: {
    customer: string
    number: string
    total: string
  }
}

const props = defineProps<InvoiceProps>()

definePdf<InvoiceProps>({
  title: ({ invoice }) => `Invoice ${invoice.number}`,
  filename: ({ invoice }) => `invoice-${invoice.number}.pdf`,
  language: 'en-GB',
  sampleData: {
    invoice: {
      customer: 'Ada Lovelace',
      number: 'INV-001',
      total: 'EUR 1,250.00',
    },
  },
})
</script>

<template>
  <PdfDocument>
    <PdfPage
      size="A4"
      :style="{ color: '#17201b', fontSize: 11, padding: 48 }"
    >
      <PdfText :style="{ fontSize: 24, marginBottom: 24 }">
        Invoice {{ props.invoice.number }}
      </PdfText>
      <PdfText>{{ props.invoice.customer }}</PdfText>
      <PdfText :style="{ marginTop: 12 }">
        Total: {{ props.invoice.total }}
      </PdfText>
      <PdfText
        fixed
        :style="{
          bottom: 24,
          color: '#68736b',
          fontSize: 8,
          position: 'absolute',
          right: 48,
        }"
        :render="({ pageNumber, totalPages }) =>
          `Page ${pageNumber} of ${totalPages}`"
      />
    </PdfPage>
  </PdfDocument>
</template>

definePdf supplies static metadata plus the sampleData the preview renders. That sample is for development only. The production transform removes it. PDF templates and their descendants can use primitives such as PdfDocument, PdfPage, and PdfText without imports.

Add a server route

Render the template from a Nitro route through the typed #pdf registry:

server/api/invoice.get.ts
import { pdf } from '#pdf'

export default defineEventHandler(async () => {
  const result = await pdf.invoice.render({
    invoice: {
      customer: 'Ada Lovelace',
      number: 'INV-001',
      total: 'EUR 1,250.00',
    },
  })

  return result.response()
})

pdf.invoice.render(props) infers props from the SFC. result.response() returns a Response with content-type: application/pdf. The generated template handle is intentionally only key, resolveMetadata, and render; preview fixtures are not a server-route data API.

Open the preview

Start Nuxt and open, in development:

  • http://localhost:3000/_pdf opens the template index.
  • http://localhost:3000/_pdf/invoice opens the browser preview.
  • http://localhost:3000/api/invoice opens the production-style route.

The /_pdf routes exist only in development.

If #pdf imports are untyped or /_pdf 404s right after enabling the module, restart nuxt dev (or run nuxt prepare) once so Nuxt writes the registry and mounts the preview handler.

What you have now

  • A discovered template with typed props and sample data.
  • A typed server route that renders and returns a completed PDF response.
  • A development preview that swaps sample data and named scenarios and shows per-render diagnostics.

Next, read the authoring model to learn the primitives and the styles-not-CSS rules, or add a table of contents.