Skip to main content

Render your first PDF

Create, download, and preview a small invoice in a Nuxt application.

This tutorial starts after you install the module. It creates one invoice from typed data and returns it from a Nitro route.

Create the invoice template

Create pdfs/invoice.vue at the application root:

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`,
  sampleData: {
    invoice: {
      customer: 'Ada Lovelace',
      number: 'INV-001',
      total: 'EUR 1,250.00',
    },
  },
})
</script>

<template>
  <PdfDocument>
    <PdfPage :style="{ 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>
    </PdfPage>
  </PdfDocument>
</template>

This is the minimum authoring contract:

  • Templates live under pdfs/.
  • The relative filename becomes the registry key. pdfs/invoice.vue becomes pdf.invoice; pdfs/reports/monthly.vue becomes pdf['reports/monthly'].
  • Each template calls definePdf() exactly once.
  • The template has one PdfDocument root and at least one PdfPage.
  • Text belongs inside PdfText.
  • Props are typed with defineProps() and passed to render().
  • Style keys use camelCase, such as fontSize and marginBottom.
  • sampleData and preview scenarios exist only in development.

Add a Nitro route

Create server/api/invoice.get.ts:

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 is generated from the files under pdfs/. Its render() method infers the props from the matching Vue template. It is available to server code only.

Start Nuxt

Run the application's normal development command:

Terminal
pnpm dev

Download the result

Keep the development server running. In another terminal, download the PDF:

Terminal
curl -o invoice.pdf http://localhost:3000/api/invoice

Open invoice.pdf in a PDF viewer. The route sends application/pdf with the filename resolved by definePdf().

Open the development preview

Open http://localhost:3000/_pdf/invoice. The preview uses sampleData and shows render diagnostics. The /_pdf routes and preview data do not exist in a production build.

The next useful concepts are templates and the registry and the document tree.