Skip to content
SnapHTML
Esc
navigateopen⌘Jpreview
On this page

Templates

Save an HTML document once, then render it per customer by sending an ID and a few variables.

A template is an HTML document stored on your account with {{ placeholders }} where the values change. Your app then sends an ID and a small JSON object instead of a full document on every request — which keeps the markup out of your codebase and the payload small.

Reach for a template when the same document is produced over and over — invoices, receipts, tickets, certificates, order confirmations. Send html directly when each document is genuinely one-off.

Create one

Templates are managed in the dashboard under Templates. Write or paste the HTML, give it a name (up to 120 characters), and save.

<!doctype html>
<html>
  <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 48px; }
      .total { font-size: 32px; font-weight: 600; }
    </style>
  </head>
  <body>
    <h1>Invoice {{ invoiceNumber }}</h1>
    <p>Billed to {{ customerName }}</p>
    <p class="total">{{ total }}</p>
  </body>
</html>

Saving parses the HTML and records the placeholder names it finds, so the dashboard can list a template’s variables for you. Editing the HTML re-parses them.

Creating, editing, and deleting templates are free — they don’t count as renders. Preview in the dashboard draws the HTML in your browser and is free too; only Render calls the API and counts.

Placeholder syntax

A placeholder is a name in double braces: {{ customerName }}. Surrounding whitespace is ignored, so {{customerName}} and {{ customerName }} are the same placeholder.

Names follow JavaScript identifier rules — letters, digits, _, and $, not starting with a digit. {{ line_total }} and {{ $ref }} work; {{ line-total }}, {{ items[0] }}, and {{ user.name }} do not, and are left in the output verbatim.

There are no loops, conditionals, or expressions. If a document needs a variable-length table, build that HTML in your app and send it as html.

Render one

POST /v1/render/template with the template’s ID and the values to substitute:

curl -X POST https://api.snhtml.com/v1/render/template \
  -H "authorization: Bearer snhtml_..." \
  -H "content-type: application/json" \
  -d '{
    "templateId": "k57d2...",
    "variables": {
      "invoiceNumber": 1842,
      "customerName": "Ada Lovelace",
      "total": "$249.00"
    },
    "format": "pdf",
    "pdfFormat": "A4"
  }' \
  --output invoice-1842.pdf

Values may be strings, numbers, booleans, or null. Each is converted to text: 1842 becomes 1842, true becomes true, and null — like a placeholder you didn’t pass at all — becomes an empty string. A missing variable is never an error, so a typo shows up as a gap in the document rather than a failed request.

The ID is a per-account secret in practice: a template only renders for the account that owns it, and a key from another account gets 404 Template not found.

Every render option works here too — the template supplies the markup, the request supplies the format, viewport, and PDF settings. The output filename defaults to the template’s name when you don’t send fileName.

Values are inserted as raw HTML

Substitution is plain text replacement, so a value containing < or & becomes markup in the rendered document. Escape anything that comes from your users before you send it:

const escapeHtml = (value: string) =>
  value.replace(
    /[&<>"']/g,
    (character) =>
      ({
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': "&quot;",
        "'": "&#39;",
      })[character] as string,
  );

await fetch("https://api.snhtml.com/v1/render/template", {
  method: "POST",
  headers: {
    authorization: `Bearer ${process.env.SNAPHTML_API_KEY}`,
    "content-type": "application/json",
  },
  body: JSON.stringify({
    templateId,
    variables: { customerName: escapeHtml(customer.name) },
    format: "pdf",
  }),
});

That behavior is occasionally what you want — a {{ rowsHtml }} placeholder filled with markup you generated. Just make sure the string was built by your code, not typed by a customer.

Was this page helpful?