Skip to main content
This page explains how to build PDF templates from scratch. To scaffold a template, run npx pdfn add invoice (see Quickstart).

Template structure

Every template is a React component that exports a Document containing one or more Page components:
pdfn-templates/my-template.tsx
import { Document, Page } from '@pdfn/react';

interface MyTemplateProps {
  title: string;
  content: string;
}

function MyTemplate({ title, content }: MyTemplateProps) {
  return (
    <Document title={title}>
      <Page size="A4" margin="1in">
        <h1>{title}</h1>
        <p>{content}</p>
      </Page>
    </Document>
  );
}

// PreviewProps provides sample data for dev server preview
MyTemplate.PreviewProps = {
  title: 'My Document',
  content: 'Hello, world!',
} satisfies MyTemplateProps;

export default MyTemplate;
Save this file in pdfn-templates/ and it will appear in the dev server preview (npx pdfn dev).

Document

The root wrapper for every PDF template. Sets metadata and loads custom fonts.
import { Document, Page } from '@pdfn/react';

export default function MyPdf() {
  return (
    <Document
      title="Invoice #001"
      author="Acme Corp"
      css={`.custom { color: red; }`}
      fonts={["Inter"]}
    >
      <Page size="A4">
        <h1>Content</h1>
      </Page>
    </Document>
  );
}

Document props

PropTypeDescription
titlestringPDF title (shows in browser tab and PDF readers)
authorstringPDF author metadata
subjectstringPDF subject metadata
keywordsstring[]PDF keywords for search
creatorstringApplication name that created the PDF
languagestringDocument language (default: "en")
cssstringCustom CSS injected into the document
fonts(string | FontConfig)[]Custom fonts to load
childrenReactNodePage components (required)

Fonts

Pass font names for Google Fonts, or font config objects for local files:
// Google Fonts
<Document fonts={["Inter", "Roboto Mono"]}>

// Local fonts
<Document fonts={[
  { family: "CustomFont", src: "./fonts/custom.woff2", weight: 400 },
]}>
See Styling — Fonts for configuration options and edge runtime considerations.

Page

Defines a page with size, margins, headers, footers, and content.
import { Document, Page, PageNumber } from '@pdfn/react';

<Document title="Invoice">
  <Page
    size="A4"
    orientation="portrait"
    margin="1in"
    background="#ffffff"
    header={<div>Acme Corp</div>}
    footer={<PageNumber />}
    watermark="DRAFT"
  >
    <h1>Page content here</h1>
  </Page>
</Document>

Page props

PropTypeDefaultDescription
sizePageSize'A4'Page dimensions
orientation'portrait' | 'landscape''portrait'Page orientation
marginstring | object'1in'Page margins
backgroundstring'#ffffff'Background color
headerReactNodeContent repeated at top of each page
footerReactNodeContent repeated at bottom of each page
watermarkstring | objectWatermark text or config
childrenReactNodePage content (required)

Page sizes

type PageSize =
  | 'A3' | 'A4' | 'A5'
  | 'Letter' | 'Legal' | 'Tabloid'
  | 'B4' | 'B5'
  | [string, string]; // Custom: [width, height] e.g. ['8.5in', '11in']

Margin formats

// Single value (all sides)
<Page margin="1in">

// Two values (vertical, horizontal)
<Page margin="1in 0.5in">

// Object (each side)
<Page margin={{ top: '1in', right: '0.5in', bottom: '1in', left: '0.5in' }}>

Watermark formats

// Simple string
<Page watermark="DRAFT">

// With options
<Page watermark={{ text: 'CONFIDENTIAL', opacity: 0.1, rotation: -35 }}>

PageNumber

Displays the current page number. Use in headers or footers.
import { Document, Page, PageNumber } from '@pdfn/react';

<Document title="Report">
  <Page
    size="A4"
    footer={
      <div style={{ textAlign: 'center' }}>
        Page <PageNumber />
      </div>
    }
  >
    <h1>Report content</h1>
  </Page>
</Document>
Accepts a className prop for styling.

TotalPages

Displays the total page count. Combine with PageNumber for “Page 1 of 5” style footers.
import { Document, Page, PageNumber, TotalPages } from '@pdfn/react';

<Document title="Report">
  <Page
    size="A4"
    footer={
      <div style={{ textAlign: 'center' }}>
        Page <PageNumber /> of <TotalPages />
      </div>
    }
  >
    <h1>Report content</h1>
  </Page>
</Document>
Accepts a className prop for styling.

PageBreak

Forces content after it to start on a new page.
import { Document, Page, PageBreak } from '@pdfn/react';

<Document title="Book">
  <Page size="A4">
    <h1>Chapter 1</h1>
    <p>First chapter content...</p>

    <PageBreak />

    <h1>Chapter 2</h1>
    <p>Second chapter content...</p>
  </Page>
</Document>

NoBreak

Keeps its children together on the same page. If the content doesn’t fit, it moves to the next page as a unit.
import { Document, Page, NoBreak } from '@pdfn/react';

<Document title="Invoice">
  <Page size="A4">
    {items.map(item => (
      <NoBreak key={item.id}>
        <h3>{item.title}</h3>
        <p>{item.description}</p>
        <p>${item.price}</p>
      </NoBreak>
    ))}
  </Page>
</Document>
Use this for content blocks that shouldn’t be split across pages, like invoice line items or signature blocks.

Thead

For tables that span multiple pages, use Thead to make headers repeat on each page.
import { Document, Page, Thead } from '@pdfn/react';

<Document title="Report">
  <Page size="A4">
    <table>
      <Thead>
        <tr>
          <th>Item</th>
          <th>Quantity</th>
          <th>Price</th>
        </tr>
      </Thead>
      <tbody>
        {items.map(item => (
          <tr key={item.id}>
            <td>{item.name}</td>
            <td>{item.quantity}</td>
            <td>${item.price}</td>
          </tr>
        ))}
      </tbody>
    </table>
  </Page>
</Document>
Thead repeats headers on each page by default. Pass repeat={false} to disable.
Standard HTML <thead> works but won’t repeat headers across pages. Use Thead for multi-page tables.
To prevent a table row from splitting across pages, use CSS: <tr style={{ breakInside: "avoid" }}>.

Next steps