# PDF options

> Paper sizes, margins, scaling, print behaviour, metadata, accessibility and PDF/UA, PDF/A archiving, attachments, protection and wait strategies for PDF output.



PDF options control how a page is printed: its size, margins, what is waited for and what metadata the file carries. Send them as `output.pdf` in a [render request](/docs/renders), or as `pdf` in the convenience endpoints.

```json
{
  "input": { "type": "template", "template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C" },
  "data": { "number": "2026-0042" },
  "output": {
    "format": "pdf",
    "pdf": {
      "paper": { "size": "A4" },
      "margin": { "top": "20mm", "right": "15mm", "bottom": "20mm", "left": "15mm" },
      "print_background": true
    }
  }
}
```

Templates store their own defaults, set in the editor's Options tab. Options you send with a request override the template's defaults.

A PDF template can also store a default e-invoice, `einvoice: {"profile": "EN16931", "invoice_path": "invoice"}` next to `pdf` in its options, set in the Options tab under &#x2A;*E-invoice (ZUGFeRD / Factur-X)**. Every render of it is then a Factur-X / ZUGFeRD PDF/A-3b unless the request sends its own `output.einvoice`, or `"einvoice": null` to render a plain PDF. On plans without e-invoicing it is skipped with the warning `einvoice_skipped_plan`. See [Make a template an e-invoice by default](/docs/e-invoicing#make-a-template-an-e-invoice-by-default).

## Page setup [#page-setup]

| Option                                                       | Type    | Default      | Description                                                                                                               |
| ------------------------------------------------------------ | ------- | ------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `paper.size`                                                 | string  | `"A4"`       | A named size: `A0` to `A6`, `B4`, `B5`, `Letter`, `Legal`, `Tabloid` or `Ledger`                                          |
| `paper.width`, `paper.height`                                | string  | —            | A custom size instead of `size`, as CSS lengths, for example `"80mm"` and `"200mm"`                                       |
| `orientation`                                                | string  | `"portrait"` | `portrait` or `landscape`. Landscape swaps width and height.                                                              |
| `margin.top`, `margin.right`, `margin.bottom`, `margin.left` | string  | not set      | Page margins as CSS lengths. When a header or footer is enabled and margins are not set, the margin follows its `height`. |
| `scale`                                                      | number  | `1.0`        | Scales the rendered content between `0.1` and `2.0`                                                                       |
| `prefer_css_page_size`                                       | boolean | `false`      | Take the page size from the CSS `@page` rule instead of `paper`, which also allows different sizes per page               |
| `single_page`                                                | boolean | `false`      | Produce a single continuous page as tall as the content                                                                   |
| `page_ranges`                                                | string  | all pages    | Keep only these pages, for example `"1-3,5"`                                                                              |

### Paper sizes [#paper-sizes]

| Size    | Millimetres   | Inches        |
| ------- | ------------- | ------------- |
| A0      | 841 × 1189    | 33.11 × 46.81 |
| A1      | 594 × 841     | 23.39 × 33.11 |
| A2      | 420 × 594     | 16.54 × 23.39 |
| A3      | 297 × 420     | 11.69 × 16.54 |
| A4      | 210 × 297     | 8.27 × 11.69  |
| A5      | 148 × 210     | 5.83 × 8.27   |
| A6      | 105 × 148     | 4.13 × 5.83   |
| B4      | 250 × 353     | 9.84 × 13.90  |
| B5      | 176 × 250     | 6.93 × 9.84   |
| Letter  | 215.9 × 279.4 | 8.5 × 11      |
| Legal   | 215.9 × 355.6 | 8.5 × 14      |
| Tabloid | 279.4 × 431.8 | 11 × 17       |
| Ledger  | 431.8 × 279.4 | 17 × 11       |

Custom sizes are useful for labels and receipts:

```json
{ "paper": { "width": "4in", "height": "6in" } }
```

### CSS length units [#css-length-units]

Sizes and margins are CSS length strings with an explicit unit:

| Unit       | Meaning                  |
| ---------- | ------------------------ |
| `mm`, `cm` | Millimetres, centimetres |
| `in`       | Inches                   |
| `pt`       | Points, 1/72 inch        |
| `pc`       | Picas, 12 points         |
| `px`       | CSS pixels, 1/96 inch    |

For example `"20mm"`, `"0.75in"` and `"48px"` are valid; a bare number is not.

## Content and print behaviour [#content-and-print-behaviour]

| Option             | Type    | Default   | Description                                                                                                                                  |
| ------------------ | ------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `print_background` | boolean | `true`    | Print background colours and images                                                                                                          |
| `emulate_media`    | string  | `"print"` | `print` applies your `@media print` rules; `screen` renders the page as a browser screen would, which is often what you want for `url` input |
| `javascript`       | boolean | `true`    | Run scripts in the page. Turn it off for untrusted HTML or to speed up renders.                                                              |
| `timezone`         | string  | `"UTC"`   | IANA time zone used by scripts in the page                                                                                                   |
| `locale`           | string  | `"en-US"` | Locale used by scripts in the page, for example for `Intl` formatting                                                                        |

Backgrounds only print when the element also allows it in CSS. Add `print-color-adjust: exact` (with the `-webkit-` prefixed property for older engines) to elements with coloured backgrounds.

## Headers and footers [#headers-and-footers]

`header` and `footer` take the same fields. [Headers and footers](/docs/headers-and-footers) explains both modes in detail.

| Option                                         | Type    | Default  | Description                                                                                               |
| ---------------------------------------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `header.enabled`                               | boolean | `false`  | Draw the header on every page                                                                             |
| `header.html`                                  | string  | none     | HTML for the header, with template expressions and the `page`, `pages`, `date`, `title` and `url` classes |
| `header.left`, `header.center`, `header.right` | string  | none     | Simple text mode, with the `{{page}}`, `{{pages}}`, `{{date}}` and `{{title}}` tokens                     |
| `header.font_size`                             | string  | `"9px"`  | Base font size of the header document                                                                     |
| `header.height`                                | string  | `"15mm"` | Height reserved for the header                                                                            |

## Accessibility and archiving [#accessibility-and-archiving]

| Option        | Type    | Default | Description                                                                                                                                                                                                                                                         |
| ------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tagged`      | boolean | `false` | Produce a tagged PDF with a structure tree, so assistive technology can read the document. Use semantic HTML (headings, lists, table headers, `alt` text) and set `metadata.lang`. The render's `result.conformance.pdfua` holds a PDF/UA-1 report for information. |
| `pdfua`       | string  | `null`  | `"1"`: deliver a PDF/UA-1 document, validated with veraPDF. The render fails if the check fails. Implies `tagged`. See [PDF/UA](#pdfua).                                                                                                                            |
| `outline`     | boolean | `false` | Add PDF bookmarks generated from the `h1` to `h6` headings                                                                                                                                                                                                          |
| `pdfa`        | string  | `null`  | `"2b"` or `"3b"`: convert the PDF to PDF/A and validate it with veraPDF. See [PDF/A](#pdfa).                                                                                                                                                                        |
| `attachments` | array   | `[]`    | Up to 20 files embedded in the PDF. See [Attachments](#attachments).                                                                                                                                                                                                |

> **Compliance features**
>
> PDF/UA and PDF/A output and e-invoices ([Factur-X, ZUGFeRD and XRechnung](/docs/e-invoicing)) are available, each with a validation report.

## PDF/A [#pdfa]

PDF/A (ISO 19005) is the standard for PDFs that must stay readable for decades, such as invoices, contracts and statements. Set `pdfa` and the engine converts the printed PDF, then validates it with veraPDF:

```json
{
  "input": { "type": "template", "template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C" },
  "data": { "number": "2026-0042" },
  "output": {
    "format": "pdf",
    "pdf": { "pdfa": "2b", "metadata": { "title": "Invoice 2026-0042", "lang": "de-DE" } }
  }
}
```

| Level | Standard                                                                | Use it for                                                                                                    |
| ----- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `2b`  | PDF/A-2b (ISO 19005-2), conformance level B: reliable visual appearance | Archiving business documents                                                                                  |
| `3b`  | PDF/A-3b (ISO 19005-3)                                                  | The same as `2b`, plus embedded files of any type. [E-invoices](/docs/e-invoicing) use it to carry their XML. |

The conversion adds an sRGB output intent and writes the PDF/A identification and the document metadata into XMP. It doesn't render the page again, so text, images and layout stay as printed. PDF/A output costs no additional renders. It is included in the Growth plan and higher; on Free and Starter, `pdfa` fails with `402 plan_feature_unavailable`.

What to know:

* **Fonts must be embedded.** If a font can't be embedded, the render fails with `422 pdfa_conversion_failed` and the message names the font.
* **No encryption.** PDF/A forbids it, so `protect` together with `pdfa` fails with `400 validation_error`.
* **Report.** `result.conformance.pdfa` on the render holds the level, the status (`passed`, `failed` or `unavailable`) and any findings. If veraPDF finds the file non-compliant, the render fails with `pdfa_conversion_failed`, costs nothing, and the failed rules are listed in the report. See [validation report](/docs/e-invoicing#validation-report).
* **Unavailable validator.** If veraPDF doesn't answer in time, the file is delivered unvalidated, with the warning `pdfa_validation_unavailable` and `status: "unavailable"`.
* **Test renders.** The TEST watermark is drawn as vector outlines and stamped before the conversion, so a test PDF is conformant too and the report describes the delivered file.

### Convert an existing PDF [#convert-an-existing-pdf]

`POST /v1/pdf-tools/pdfa` converts a PDF that wasn't rendered by Dynamic Document API and validates the result:

```bash
curl https://api-eu.dynamicdocumentapi.com/v1/pdf-tools/pdfa \
  -H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "source": { "url": "https://example.com/contract.pdf" }, "level": "2b", "filename": "contract-pdfa.pdf" }'
```

| Field      | Description                                                                                        |
| ---------- | -------------------------------------------------------------------------------------------------- |
| `source`   | The PDF. Exactly one of `url`, `render_id` (with an optional `file_id`), `upload_id` or `data_uri` |
| `level`    | `2b` or `3b`                                                                                       |
| `filename` | Name of the converted file                                                                         |

The response is a render with the converted file and the report in `result.conformance.pdfa`. It accepts the same `mode`, `delivery`, `webhook`, `reference`, `metadata` and `test` fields as the other PDF tools. Conversion is best effort: it can't embed fonts the source doesn't contain, and it can't process encrypted PDFs. Either fails with `pdfa_conversion_failed`. A conversion is billed as 1 render, and failed ones cost nothing. Like `pdfa`, this endpoint needs the Growth plan or higher.

## PDF/UA [#pdfua]

PDF/UA (ISO 14289-1) is the standard for accessible PDFs: screen readers and other assistive technology can read them in the right order, with headings, lists, tables, link texts and image descriptions. Set `pdfua` to `"1"` and the render either delivers a PDF that passes veraPDF's PDF/UA-1 checks or fails:

```json
{
  "input": { "type": "template", "template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C" },
  "data": { "number": "2026-0042" },
  "output": {
    "format": "pdf",
    "pdf": { "pdfua": "1", "metadata": { "title": "Invoice 2026-0042", "lang": "en-GB" } }
  }
}
```

After rendering, Dynamic Document API fixes what the browser's tagging leaves open: link descriptions, list structure, headers and footers marked as artifacts that screen readers skip, and the PDF/UA identification. What only your template can provide:

* **A title and a language.** Set `metadata.title` or a `<title>` element, and `metadata.lang` or `<html lang>`. With `pdfua` there is no default language: a document without one fails the check.
* **Text alternatives.** `alt` text on meaningful images, and `alt=""` on decorative ones.
* **Structure.** Real tables with `<th scope>`, headings in order, and lists as `<ul>` or `<ol>`.

Content that is hidden from the accessibility tree (`aria-hidden`, `alt=""`, CSS `content`, borders) becomes a decoration that screen readers skip, so meaningful text belongs in the markup.

What to know:

* **Report.** `result.conformance.pdfua` holds the standard, the status (`passed`, `failed` or `unavailable`) and the rules that failed. A render that fails the check fails with `422 pdfua_validation_failed` and the report, and costs nothing.
* **Unavailable validator.** If veraPDF doesn't answer in time, the file is delivered unvalidated, with the warning `pdfua_validation_unavailable` and `status: "unavailable"`.
* **Limits of the check.** veraPDF checks what a machine can check. Whether alt text is meaningful and the reading order is right is yours to review.
* **Combinations.** `pdfua` works with `pdfa`, [e-invoices](/docs/e-invoicing), attachments and `protect`, which keeps access for assistive technology. PDFs from canvas templates can't be PDF/UA (`400 validation_error`).
* **Previews and publishing.** Editor previews show the report but never fail. Publishing warns when the template's test render fails the check.
* **Plan.** PDF/UA is included in the Growth plan and higher; on Free and Starter, `pdfua` fails with `402 plan_feature_unavailable`.

## Attachments [#attachments]

`attachments` embeds files in the PDF, for example the supporting documents of an invoice. Each entry has:

| Field                            | Description                                                                                                                                                      |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`, `data_uri` or `upload_id` | Exactly one source. A `url` is fetched like an asset. A `data_uri` may be at most 5 MB, and all of them together at most 8 MB of base64; use an upload for more. |
| `name`                           | File name in the PDF: 1 to 255 characters, without `/`, `\` or control characters, and unique in the document, ignoring case                                     |
| `mime_type`                      | Optional. Derived from the name's extension, else `application/octet-stream`                                                                                     |
| `description`                    | Optional description shown by PDF readers                                                                                                                        |
| `relationship`                   | Optional: `Source`, `Data`, `Alternative`, `Supplement` or `Unspecified` (the default)                                                                           |

```json
{
  "output": {
    "format": "pdf",
    "pdf": {
      "attachments": [
        { "url": "https://example.com/timesheet.csv", "name": "timesheet.csv", "relationship": "Supplement" }
      ]
    }
  }
}
```

Up to 20 files, each at most 10 MiB and 25 MiB together. A file that can't be fetched fails the render with `422 attachment_failed`, which names it.

Attachments work with plain PDFs and with `pdfa: "3b"`, where they sit next to an e-invoice's XML. `pdfa: "2b"` can't carry them (`400 validation_error`), and an attachment can't take the name of the embedded e-invoice (`factur-x.xml`, `xrechnung.xml`). With `protect`, the files are embedded before encryption. Editor previews and test renders leave attachments out. Uploads expire after 24 hours, so a template's default attachments use `url` or `data_uri`.

## Metadata [#metadata]

| Option              | Type             | Default | Description                                                                                                                                                                                                      |
| ------------------- | ---------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `metadata.title`    | string           | none    | Document title stored in the PDF                                                                                                                                                                                 |
| `metadata.author`   | string           | none    | Author                                                                                                                                                                                                           |
| `metadata.subject`  | string           | none    | Subject                                                                                                                                                                                                          |
| `metadata.keywords` | array of strings | `[]`    | Keywords                                                                                                                                                                                                         |
| `metadata.creator`  | string           | none    | The application that created the document                                                                                                                                                                        |
| `metadata.lang`     | string           | `"en"`  | Document language as a BCP 47 tag, such as `"de-DE"`. It sets the `lang` attribute of the rendered HTML and the document language in the PDF. With `pdfua` there is no default: set it here or in `<html lang>`. |

## Security [#security]

| Option                               | Type    | Default | Description                                  |
| ------------------------------------ | ------- | ------- | -------------------------------------------- |
| `protect.user_password`              | string  | none    | Password needed to open the document         |
| `protect.owner_password`             | string  | none    | Password needed to change permissions        |
| `protect.permissions.print`          | boolean | `true`  | Allow printing                               |
| `protect.permissions.print_high_res` | boolean | `true`  | Allow printing at full quality               |
| `protect.permissions.copy`           | boolean | `true`  | Allow copying text and images                |
| `protect.permissions.modify`         | boolean | `false` | Allow changing the content                   |
| `protect.permissions.annotate`       | boolean | `false` | Allow adding comments and annotations        |
| `protect.permissions.fill_forms`     | boolean | `true`  | Allow filling in form fields                 |
| `protect.permissions.assemble`       | boolean | `false` | Allow inserting, rotating and deleting pages |

Protecting a document restricts editing, not reading: a permission you leave out keeps its default. To protect an existing PDF, use the [protect tool](/docs/pdf-tools).

Documents are encrypted with AES-256. Passwords are part of the request body, so keep them out of logs on your side and use request-log redaction rules if you enable request logging. Protection can't be combined with `pdfa` or an e-invoice, because PDF/A forbids encryption. With `pdfua`, protection keeps the permission that lets assistive technology read the text.

## Waiting and scripting [#waiting-and-scripting]

Rendering starts once the page is ready. Choose a strategy that matches how your page loads:

| `wait.until`  | Ready when                                                                                                   |
| ------------- | ------------------------------------------------------------------------------------------------------------ |
| `load`        | The page's `load` event has fired                                                                            |
| `networkidle` | No request has been in flight for 500 ms. The default, and a good fit for pages with remote images or fonts. |
| `selector`    | An element matching `wait.selector` exists                                                                   |
| `ready_flag`  | The page has set `window.__DYNAMIC_DOCUMENT_API_READY__ = true`                                              |
| `delay`       | `wait.delay_ms` milliseconds have passed after load                                                          |

| Option            | Type   | Default         | Description                                        |
| ----------------- | ------ | --------------- | -------------------------------------------------- |
| `wait.until`      | string | `"networkidle"` | Wait strategy, see above                           |
| `wait.selector`   | string | none            | CSS selector to wait for, with `until: "selector"` |
| `wait.delay_ms`   | number | `0`             | Extra delay in milliseconds, up to 10000           |
| `wait.timeout_ms` | number | `30000`         | Maximum time for each wait step                    |

After the wait condition, the renderer always waits for web fonts to load and images to decode before printing, so a fixed `delay` is rarely necessary. When a wait step times out, the render continues with the page as it is and records a warning, so check the render's `warnings` if output looks incomplete. A render that cannot continue fails with `422 wait_timeout`.

For pages that build content with JavaScript, such as charts, set the ready flag when you are done:

```html
<script>
  renderChart(data).then(() => {
    window.__DYNAMIC_DOCUMENT_API_READY__ = true;
  });
</script>
```

```json
{ "wait": { "until": "ready_flag", "timeout_ms": 20000 } }
```

## Examples [#examples]

An A4 invoice with a footer and metadata:

```json
{
  "paper": { "size": "A4" },
  "margin": { "top": "18mm", "right": "16mm", "bottom": "22mm", "left": "16mm" },
  "print_background": true,
  "footer": {
    "enabled": true,
    "html": "<div style=\"width:100%;padding:0 16mm;display:flex;justify-content:space-between;font-size:8pt;color:#555\"><span>Example GmbH</span><span>Page <span class=\"page\"></span> of <span class=\"pages\"></span></span></div>",
    "height": "14mm"
  },
  "metadata": { "title": "Invoice 2026-0042", "author": "Example GmbH", "lang": "de-DE" }
}
```

An 80 mm receipt on one continuous page, where the declared height is replaced by the content height:

```json
{
  "paper": { "width": "80mm", "height": "200mm" },
  "margin": { "top": "4mm", "right": "4mm", "bottom": "4mm", "left": "4mm" },
  "single_page": true
}
```

A landscape report with bookmarks and a tagged structure:

```json
{
  "paper": { "size": "A3" },
  "orientation": "landscape",
  "scale": 0.9,
  "outline": true,
  "tagged": true,
  "metadata": { "title": "Quarterly report", "lang": "en-GB" }
}
```

A shipping label at 4 × 6 inches, printed edge to edge:

```json
{
  "paper": { "width": "4in", "height": "6in" },
  "margin": { "top": "0mm", "right": "0mm", "bottom": "0mm", "left": "0mm" },
  "print_background": true
}
```

A protected document with printing allowed but copying and editing blocked:

```json
{
  "paper": { "size": "A4" },
  "protect": {
    "user_password": "…",
    "owner_password": "…",
    "permissions": { "print": true, "copy": false, "modify": false }
  }
}
```

## Related pages [#related-pages]

* [Headers and footers](/docs/headers-and-footers) for running headers, page numbers and logos
* [Pagination basics](/docs/pagination) for page breaks, repeating table headers and layout problems
* [Image and screenshot options](/docs/image-options) for PNG and JPEG output
* [E-invoicing](/docs/e-invoicing) for Factur-X, ZUGFeRD and XRechnung
* [PDF tools](/docs/pdf-tools) for merging, splitting, protecting and converting existing PDFs
