Create a template

Templates are the layout that PDF generation renders against your data. Design them visually with drag-and-drop, write raw HTML/CSS with Handlebars, or create them through the API.

Getting started

1
Open Templates

From your dashboard, open All templates in the sidebar and click New template. Choose Code + Handlebars or Visual designer.

2
Design your layout

In the visual designer, place text blocks, images, tables, and shapes with drag-and-drop. In code mode, write HTML and CSS directly.

3
Add data bindings

Use Handlebars syntax anywhere in the template to inject values from your JSON payload at generation time. For example: {{customer.name}}.

4
Define a schema (optional)

Add a JSON Schema to document expected input fields. Agents use this to construct correctly-shaped payloads when generating PDFs via MCP.

5
Publish and copy the ID

Click Publish. Generation always uses the latest published version, so draft edits have no effect until you publish again. Copy the Template ID (a UUID) — you'll pass it as templateId in generate requests.

Page size and orientation

Set page size (A4, Letter, or Legal), orientation (portrait/landscape), margins, headers and footers in the template's PDF settings. They are saved with the template and cannot be changed per generation request.

Assets

Upload logos, images, and custom fonts from Settings → Assets. Use the returned renderPath in your template HTML or CSS. See Template assets for details.

Create a template via the API

With an API key that has the template:write scope you can create, edit and publish templates from code:

  • POST /v1/templates with name (1–200 characters), optional description (up to 1,000) and optional mode (code, visual or pdfox-builder; default code). Returns 201 with the template.
  • PATCH /v1/templates/{id} updates the draft. Fields: html, css, sampleJson (a JSON object, not a string), schema, bindings, grapesjs, builderSchema, metadata, name, description. Unknown fields such as draftHtml are silently ignored.
  • POST /v1/templates/{id}/publish snapshots the draft into a new version and returns { "version": { "id", "versionNumber", ... } }. The body is optional; pass sourceVersionId to re-publish an earlier version. Returns 422 UNPROCESSABLE if the draft HTML is empty, or if this is the first publish and your plan's template limit is reached.

GET /v1/templates/{id} returns the draft as draftHtml, draftCss, draftSchema and draftSampleJson, with the authoring mode in metadata.mode. GET /v1/templates (scope template:read) returns { "templates", "templateLimit" }: up to 50 templates, most recently updated first.

curl -X POST https://api.pdf-fox.com/v1/templates   -H "Authorization: Bearer $PDFOX_API_KEY"   -H "Content-Type: application/json"   -d '{ "name": "Invoice Template", "mode": "code" }'

# 201 Created → { "id": "a3f1d2e4-1111-4562-b3fc-2c963f66afa6", ... }

Payload validation (optional)

By default PDFox accepts any data object and leaves optional fields empty. To reject requests that don't match your template's JSON Schema instead, turn on Reject requests that don't match the schema in the editor's Settings tab, or send { "metadata": { "validatePayload": true } } in PATCH /v1/templates/{id}.

  • Checked against the published version's schema, before any credit is used. Draft edits don't count until you publish.
  • A mismatch returns 400 VALIDATION_ERROR with a details entry per failing field, e.g. { "field": "data.items.0.qty", "message": "must be integer" }.
  • If the schema itself is invalid you get 422 TEMPLATE_SCHEMA_INVALID; fix and republish it, or turn the setting off.
  • The setting takes effect immediately and applies to REST, MCP and dashboard generation. Retries of an already-accepted job aren't re-checked.
AI agents connected via MCP can create and iterate on template drafts but cannot publish them — a person publishes from the dashboard. See Template drafts via MCP.

Template ID

The template ID is a UUID, for example a3f1d2e4-1111-4562-b3fc-2c963f66afa6. It appears in the template editor URL and is returned as id by the API. Pass it as templateId in every generate request.