---
name: pdfmonkey-templates
description: Build, preview and publish PDFMonkey document templates (Code mode with Liquid and Tailwind, or visual Builder mode) with the PDFMonkey CLI. Use when the user asks for an invoice, contract, certificate, report or any other PDF or image template on PDFMonkey.
---

# Build PDFMonkey templates

PDFMonkey generates PDFs and images from a **template** (layout) and a **JSON payload** (data). Your job is to write the template files, check them in the live preview, and publish them. Templates come in two mutually exclusive modes: **Code** (HTML + Liquid + Tailwind CSS) and **Builder** (block components). Ask which one the user wants if it is not clear; Code is the default.

Docs: https://pdfmonkey.io/docs/ (every page also exists as Markdown, see https://pdfmonkey.io/llms.txt).

## Workflow

Requires Node.js 22.12+ and an API key in `PDFMONKEY_API_KEY` (found on https://dashboard.pdfmonkey.io/account). Never print or commit the key.

1. Install the CLI if missing: `npm install -g @pdfmonkey/cli`, then run `pdfmonkey whoami` to confirm the key works.
2. Create a directory with the three files the CLI syncs, all three must exist:
   - `body.html.liquid`: the template body
   - `styles.scss`: extra CSS (may be empty)
   - `sample_data.json`: the test payload used by the preview
3. Create the template from that directory: `pdfmonkey template create -w "<workspace name or id>" -n "<name>" --from ./dir` (add `-m builder` for Builder mode; `pdfmonkey workspace list` lists workspaces). The directory is now linked to the template (`.pdfmonkey.json`).
4. Preview: `pdfmonkey template watch ./dir -o` syncs every save and serves a live preview on http://localhost:2081. Iterate until it looks right, then check the result visually if you can (screenshot of the preview).
5. Test a real generation: publish first (`pdfmonkey template publish <template-id>`, documents are generated from the **published** version only), then `pdfmonkey document create -t <template-id> --payload @dir/sample_data.json -o ./out/`.
6. Edits sent with `--from` or `watch` are saved as a **draft**. Do not publish an existing, live template without asking the user: publishing changes what their production documents look like.

Every management command accepts `--json` for machine-readable output. `pdfmonkey help` and `pdfmonkey <resource> --help` list all options. Full CLI reference: https://pdfmonkey.io/docs/integrations/cli/

Paper format, orientation, margins, header and footer are **template settings**, not part of the local files. Read the current ones with `pdfmonkey template get <template-id> --json`. If the document needs other values, tell the user to change it in the Settings tab of the template, or update `settings_draft` through the Templates API (https://pdfmonkey.io/docs/api/templates/). Settings keys: `paper_format`, `orientation`, `paper_width` and `paper_height` (mm, for `custom`), `margin` (`top`, `right`, `bottom`, `left`), `header`, `footer`.

The PDFMonkey MCP server can read templates and generate documents but cannot create or edit templates: use the CLI for building.

## Rules for every template

- Produce a complete, realistic document that renders correctly right away: no placeholders, no TODO comments.
- `sample_data.json` holds realistic mock values (company "Acme Corp", amounts like 1234.56, real-looking addresses and dates). For collections (line items, rows) include 3 to 5 entries so multi-row rendering is visible.
- For image placeholders use inline SVG data URIs, never external placeholder services (via.placeholder.com, placehold.co…): they block PDF rendering. Example:
  `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='60' viewBox='0 0 200 60'%3E%3Crect width='200' height='60' fill='%23e2e8f0'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%2394a3b8' font-size='14' font-family='sans-serif'%3ELogo%3C/text%3E%3C/svg%3E`
  Adapt width, height and label (Logo, Photo, Signature…).
- Infer sensible settings from the document type: invoice, contract, report → A4 portrait; certificate → A4 landscape; business card → custom size.
- Rendering uses Chromium: standard CSS print rules apply.

## Code mode (Liquid + Tailwind CSS)

- Start `body.html.liquid` with the Tailwind 4 script: `<script src="https://pdfmonkey-resources.s3-eu-west-3.amazonaws.com/js/tailwindcss-4.js"></script>`
- Style with Tailwind utility classes. Use `<style type="text/tailwindcss">` only to customize Tailwind (`@theme`, utilities). Plain CSS can go in `styles.scss`.
- Dynamic content is Liquid: `{{ variable }}` for output, `{% for %}`, `{% if %}`, `{% unless %}`, `{% assign %}`, `{% capture %}` for logic. Use snake_case variable names (`company_name`, `line_items`), matching the keys of `sample_data.json`.
- Multi-page documents: force a page break with `class="break-before-page"` on the section that starts the next page.
- Do not enable `inject_javascript` in settings unless the template really needs JavaScript (https://pdfmonkey.io/docs/code-templates/custom-javascript/).

### Liquid filters

Filters are strict: **an unregistered filter fails at render time**. Use only these.

Standard Liquid: `append`, `prepend`, `upcase`, `downcase`, `capitalize`, `strip`, `lstrip`, `rstrip`, `truncate`, `truncatewords`, `split`, `join`, `first`, `last`, `size`, `reverse`, `sort`, `sort_natural`, `uniq`, `map`, `compact`, `concat`, `where`, `date`, `default`, `escape`, `url_encode`, `url_decode`, `strip_html`, `strip_newlines`, `newline_to_br`, `abs`, `at_least`, `at_most`, `ceil`, `divided_by`, `floor`, `minus`, `modulo`, `plus`, `round`, `times`.

PDFMonkey filters:

- `push`: append an item to an array. `{{ my_array | push: "new_item" }}`
- `slice_by`: split an array into chunks of N. `{{ items | slice_by: 3 }}`
- `sum`: sum numeric values. `{{ line_items | map: "amount" | sum }}`
- `to_sentence`: join with a connector. `{{ names | to_sentence: ", ", " and " }}`
- `where_exp`: filter with an expression. `{{ items | where_exp: "item", "item.active == true" }}`
- `in_time_zone`: convert a date to a time zone. `{{ created_at | in_time_zone: "Europe/Paris" }}`
- `ensure_protocol`: prepend a protocol if missing. `{{ link | ensure_protocol: "https" }}`
- `format`: sprintf-style formatting. `{{ price | format: "%.2f" }}`
- `with_delimiter`: thousands separators. `{{ amount | with_delimiter }}`
- `entities`: HTML entity encoding. `{{ raw_text | entities }}`
- `json`: convert to a JSON string. `{{ data | json }}`
- `parse_json`: parse a JSON string. `{{ json_string | parse_json }}`
- `money`: currency format. `{{ amount | money: "en" }}`
- `money_format`: currency with a custom format. `{{ amount | money_format: "en", "%n %u" }}`

Not available: array grouping, rejecting or finding filters, and any date formatting other than `date`. Reference: https://pdfmonkey.io/docs/code-templates/filters/

## Builder mode (block components)

Create with `-m builder`. `body.html.liquid` is the **preamble** followed by the template. The dashboard's Builder adds the preamble itself when it saves, but a body sent through the CLI or the API has to carry it: **without it the document renders as a blank page**.

```html
<!-- PDFMonkey Vue Template -->
<!-- Snapshot window keys before user assets load -->
<script>window.__pmBaseKeys=new Set(Object.keys(window))</script>

<!-- Vue 3 Runtime + Compiler -->
<script src="https://pdfmonkey-resources.s3-eu-west-3.amazonaws.com/builder/vue-global-prod-3.5.28.js"></script>
<!-- Tailwind CSS v4 Browser Runtime -->
<script src="https://pdfmonkey-resources.s3-eu-west-3.amazonaws.com/builder/tailwind-browser-4.1.18.js"></script>
<!-- PDFMonkey Builder Runtime -->
<script src="https://builder.pdfmonkey.io/runtime.js"></script>
<!-- DO NOT EDIT ABOVE THE SEPARATOR -->
<!-- PDFMONKEY-TEMPLATE-START -->
<template>
  <page-block class="relative bg-[#ffffff] paper-format-a4 paper-orientation-portrait w-[795px] h-[1123px] pt-[32px] pb-[32px] pl-[32px] pr-[32px]">
    {content made of block components}
  </page-block>
</template>
```

The preamble above is the default one. Library versions can change: if the user already has a Builder template, copy the exact preamble from it (`pdfmonkey template get <template-id> --json`, field `body_draft`, everything up to and including `<!-- DO NOT EDIT ABOVE THE SEPARATOR -->`). Never edit the preamble. Only the part after `<!-- PDFMONKEY-TEMPLATE-START -->` is yours: no other `<script>`, no `<div id="app">` wrapper.

### Blocks

- `<page-block>`: page container, must be the root element inside `<template>`. Multiple pages are sibling `<page-block>` elements. A page has a fixed height and content does not flow onto the next page.
- `<container-block>`: flex container for rows, columns and sections. Typical classes: `flex flex-col gap-y-[16px] pt-[10px] pb-[10px] pl-[10px] pr-[10px]`. Nest other blocks inside.
- `<heading-block tag="h2" class="text-[24px] font-bold">`: heading, h1 by default, level set with `tag` (h1 to h6).
- `<text-block class="text-[14px] text-[#666666]">`: rich text. Wrap all text in `<p>`. Allowed inner tags: `p`, `strong`, `em`, `u`, `a target="_blank" rel="noopener noreferrer nofollow"`, `ul`, `ol`, `li`, `blockquote`, `pre` with `code`, `hr`, `br`.
- `<image-block>`: image, classes `max-w-[100%] h-[auto]`. Dynamic URL: `<image-block :src="logoUrl" alt="Logo" class="w-[120px]"></image-block>`. Static placeholder: `src="data:image/svg+xml,…"`.
- Tables use standard HTML, not a `-block` suffix: `<table class="w-[100%] border-collapse">` with `thead`, `tbody`, `tr`, `th`, `td`. `th` classes: `pl-[8px] pr-[8px] pt-[8px] pb-[8px] text-left font-bold bg-[#f3f4f6]`; `td`: `pl-[8px] pr-[8px] pt-[8px] pb-[8px]`.

Rules that break the template when ignored:

- **Never self-close a block** (`<image-block … />`): the HTML parser keeps the tag open and it swallows every following sibling. Always write the closing tag.
- Do not use `div`, `span`, `p`, `section`, `h1`… for structure: the builder drops them with everything inside. Raw HTML is only allowed inside `<text-block>` and `<heading-block>`.
- No `<style>` or `<script>` tags: the builder drops them.
- The Builder has more blocks (HTML, QR code, conditions and loops in the visual panels): https://pdfmonkey.io/docs/builder-templates/available-blocks/ . Only use blocks you know.

### Data in blocks

Payload properties are available directly by name, with no prefix, and keys are **camelCase** in `sample_data.json` (`companyName`, `lineItems`, `invoiceDate`).

- Output: `{{ companyName }}`
- Loops: `v-for="(item, index) in lineItems" :key="index"` (must be an array: objects and numbers render nothing)
- Conditions: `v-if="showDiscount"`
- Conditional classes: object syntax with single-quoted keys only, `:class="{ 'text-[#b91c1c]': isOverdue }"`
- Dynamic attributes: `:src="logoUrl"`, `:href="website"`

### Styling

Use Tailwind v4 classes in the exact forms the builder style panel can edit:

- Padding and margin, one class per side: `pt-[16px] pr-[16px] pb-[16px] pl-[16px]`, `mt-[8px]`… (not `p-[16px]`, `px-4`). No margin on `page-block`.
- Gap: `gap-x-[16px] gap-y-[16px]` (not `gap-[16px]`).
- Borders, one width class per side plus a color: `border-t-[1px] border-r-[1px] border-b-[1px] border-l-[1px] border-[#e5e7eb]` (not `border`).
- Radius, per corner: `rounded-tl-[8px] rounded-tr-[8px] rounded-br-[8px] rounded-bl-[8px]` (not `rounded-lg`).
- Colors: 6-digit lowercase hex in arbitrary values, `text-[#374151]`, `bg-[#f3f4f6]` (not `text-gray-700`, `bg-white`).
- Font size `text-[14px]`; weight by name: `font-normal`, `font-medium`, `font-semibold`, `font-bold` (not `font-[700]`).
- Sizing: `w-[100%]`, `w-[240px]`, `h-[auto]`, `max-w-[100%]`, `flex-[1]` (not `w-full`, `flex-1`).
- Layout: `flex`, `flex-row`, `flex-col`, `items-center`, `justify-between`, `grid`, `grid-cols-3`, `col-span-2`.
- Row styling inside loops: `odd:bg-[#f9fafb]`, `last:border-b-[0px]` on the repeated element.

### Page formats

Settings use mm, `page-block` uses px: px = mm × 96 / 25.4 (≈ mm × 3.77953).

- A4 portrait: `paper-format-a4 paper-orientation-portrait w-[795px] h-[1123px]`
- A4 landscape: `paper-format-a4 paper-orientation-landscape w-[1123px] h-[795px]`
- Letter portrait: `paper-format-letter paper-orientation-portrait w-[815px] h-[1056px]`
- Letter landscape: `paper-format-letter paper-orientation-landscape w-[1056px] h-[815px]`
- Custom: convert `paper_width` and `paper_height` (mm) to px.

Keep the `page-block` size consistent with the template's paper settings.
