# Record PDF Export
URL: /docs/web/docyrus/record-pdf-export
A three-step wizard (Select a Template → Preview → PDF) that renders a Docyrus data source record to PDF using its saved HTML templates, with an optional editable-template mode.
**Record PDF Export** turns a single Docyrus data source record into a PDF through a guided
three-step wizard:
1. **Select a Template** — lists the data source's saved HTML/PDF templates (`tenant_html_template`).
2. **Preview** — renders the chosen template compiled against the record, using the
[`HtmlTemplateEditor`](/docs/web/components/html-template-editor) in **preview-only** mode.
3. **PDF** — generates the final PDF server-side and shows it in an inline viewer with a download button.
4. **Email** *(optional, when `sendEmail` is set)* — mounts an email composer
([`useDocyrusEmailComposer`](/docs/web/hooks/use-docyrus-email-composer)) with the generated PDF
pre-attached, so the user can send the record straight from the wizard.
The headless [`useDocyrusRecordPdfExport`](#hook--usedocyrusrecordpdfexport) hook owns all data
fetching, step state, and PDF generation. `DocyrusRecordPdfExportWizard` is the batteries-included
UI built on top of it. The hook accepts a **`dataSourceId`** and **`recordId`** — the app / data
source slugs needed by the render endpoints are resolved automatically (or supplied explicitly).
**Demo:**
```tsx
'use client';
import { FileTextIcon } from 'lucide-react';
import { Step, Stepper } from '@docyrus/ui/components/stepper';
import { Button } from '@docyrus/ui/primitives/ui/button';
export function RecordPdfExportDemo() {
return (
{''} requires an authenticated{' '}
RestApiClient plus a dataSourceId and{' '}
recordId. It lists the data source's saved HTML
templates, previews the selected template compiled against the record
(via the HtmlTemplateEditor in preview-only mode), and
renders a server-side PDF — wire it to your tenant (or the playground)
to see it live.
);
}
```
## Installation
```bash
pnpm dlx @docyrus/cli add @docyrus/ui-record-pdf-export
```
**Dependencies:**
- [@tanstack/react-query](https://www.npmjs.com/package/@tanstack/react-query)
- [lucide-react](https://www.npmjs.com/package/lucide-react)
- [@docyrus/api-client](https://www.npmjs.com/package/@docyrus/api-client)
## Usage
### Wizard inside a dialog
```tsx
import { useState } from 'react';
import { useDocyrusAuth } from '@docyrus/signin';
import { DocyrusRecordPdfExportWizard } from '@docyrus/ui/components/record-pdf-export';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger
} from '@docyrus/ui/primitives/ui/dialog';
import { Button } from '@docyrus/ui/primitives/ui/button';
function ExportRecordButton({ dataSourceId, recordId }: { dataSourceId: string; recordId: string }) {
const { client } = useDocyrusAuth();
const [open, setOpen] = useState(false);
if (!client) return null;
return (
);
}
```
### Editable-template mode
Pass `templateEditable` to let the user tweak the rendered document before exporting. The preview
step then exposes the **Code**, **Data** (read-only reference), and **Preview** tabs — the Code tab
holds the server-compiled document HTML for this record. When it is edited, the PDF step renders the
edited HTML through the custom-body endpoint instead of the saved template.
```tsx
```
### Email step
Pass `sendEmail` to append an **Email** step. After the PDF is generated, the wizard mounts an email
composer (via [`useDocyrusEmailComposer`](/docs/web/hooks/use-docyrus-email-composer)) with the
rendered PDF pre-attached (sent through its storage path) and the user's email accounts loaded into
the From selector. Sending requires the `Messaging.Email.Send` OAuth scope.
```tsx
```
### Headless — drive your own UI
```tsx
import { HtmlTemplateEditor } from '@docyrus/ui/components/html-template-editor';
import { useDocyrusRecordPdfExport } from '@docyrus/ui/components/record-pdf-export';
function CustomExport({ client, dataSourceId, recordId }) {
const {
step, goNext, goBack, canGoNext,
templates, selectedTemplateId, setSelectedTemplateId,
previewEditorProps,
pdfUrl, isGeneratingPdf
} = useDocyrusRecordPdfExport({ client, dataSourceId, recordId });
// Step 1 — render `templates` and call `setSelectedTemplateId(id)`.
// Step 2 —
// Step 3 — (auto-generated on entering the step).
}
```
## How it works
| Step | Endpoint(s) | Notes |
|------|-------------|-------|
| Resolve slugs | `GET /v1/dev/data-sources/:dataSourceId` | Maps `dataSourceId` → `app_slug` + `slug`. Skipped when `appSlug` + `dataSourceSlug` are supplied. |
| List templates | `GET /v1/dev/html-templates?tenantDataSourceId=:dataSourceId` | Drives the "Select a Template" step. The default template (`is_default`) is auto-selected. |
| Compiled preview | `GET /v1/apps/:appSlug/data-sources/:dataSourceSlug/items/:recordId/templates/:templateId/html` | Returns the **server-compiled** document `body` (+ `styles`) for this record — the same `templateCompiler` the PDF uses. This is the preview source, so the preview matches the PDF exactly. |
| Record data (editable only) | `GET /v1/apps/:appSlug/data-sources/:dataSourceSlug/items/:recordId?columns=*` | Read-only reference shown in the editable-mode Data tab. |
| Render PDF (saved) | `GET /v1/apps/:appSlug/data-sources/:dataSourceSlug/items/:recordId/templates/:templateId/pdf` | Default path. The server compiles the saved template against the record. |
| Render PDF (edited) | `PUT /v1/apps/:appSlug/data-sources/:dataSourceSlug/items/:recordId/renderPdfTemplate/:templateId` | Used in `templateEditable` mode when the compiled HTML was edited. Sends the edited HTML as a `multipart/form-data` `body` file (`data={}` so the server loads the record for header/footer). |
## Preview uses the HTML Template Editor
The preview step mounts [`HtmlTemplateEditor`](/docs/web/components/html-template-editor) with the
**server-compiled** document HTML as `value`, then restricts the visible tabs. Compiling on the
server (rather than client-compiling the raw template against the raw record) is what makes the
preview faithful — the record's fields are mapped/expanded exactly as they are for the PDF. Three
editor props power this:
| Prop | Type | Purpose |
|------|------|---------|
| `visibleTabs` | `HtmlTemplateEditorTab[]` | Restricts which tabs render (and their order). The wizard passes `['preview']` (read-only) or `['code', 'data', 'preview']` (editable). The tab strip auto-hides when a single tab remains, and the Visual/Plate tab is intentionally omitted (its round-trip is lossy for complex nested-block table templates). |
| `dataReadOnly` | `boolean` | Makes only the **Data** tab read-only while keeping the Code editor editable — the record data is a reference, not editable. |
| `previewStyles` | `string` | Extra CSS injected into the Preview iframe (the template's `styles`), without baking a `