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
| Prop | Type | Description |
|---|
title | string | PDF title (shows in browser tab and PDF readers) |
author | string | PDF author metadata |
subject | string | PDF subject metadata |
keywords | string[] | PDF keywords for search |
creator | string | Application name that created the PDF |
language | string | Document language (default: "en") |
css | string | Custom CSS injected into the document |
fonts | (string | FontConfig)[] | Custom fonts to load |
children | ReactNode | Page 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
| Prop | Type | Default | Description |
|---|
size | PageSize | 'A4' | Page dimensions |
orientation | 'portrait' | 'landscape' | 'portrait' | Page orientation |
margin | string | object | '1in' | Page margins |
background | string | '#ffffff' | Background color |
header | ReactNode | — | Content repeated at top of each page |
footer | ReactNode | — | Content repeated at bottom of each page |
watermark | string | object | — | Watermark text or config |
children | ReactNode | — | Page content (required) |
Page sizes
type PageSize =
| 'A3' | 'A4' | 'A5'
| 'Letter' | 'Legal' | 'Tabloid'
| 'B4' | 'B5'
| [string, string]; // Custom: [width, height] e.g. ['8.5in', '11in']
// 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' }}>
// 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