API

Templating

cloudlayer.io uses the Nunjucks templating engine to power its template-based document generation. With templates, you can create professional, data-driven PDFs and images — invoices, certificates, reports, receipts, and more — by combining HTML/CSS layouts with dynamic JSON data.

Overview

All templates support:

  • Nunjucks templating syntax — variables, conditionals, loops, filters, and macros
  • Built-in Tailwind CSS — link the CDN in your templates for utility-first styling
  • Custom formatting functionsformatCurrency() and formatDateTime() for multicultural support
  • Auto calculations — predefined templates can compute totals, taxes, and subtotals automatically
  • Smart page breaking — PDF templates handle page breaks intelligently for multi-page documents
  • Thumbnail generation — optionally generate image previews of your documents

Template Types

There are two ways to use templates with cloudlayer.io: predefined templates from the gallery, or custom templates you build yourself.

Predefined Templates

The fastest way to get started. Browse the template gallery, pick a design, and pass your data.

  1. Choose a template from the PDF Template Gallery or the Image Template Gallery.
  2. Copy the templateId from the gallery page.
  3. Use the sample JSON data as a starting point and customize it with your own values.
{
  "templateId": "professional-invoice",
  "data": {
    "company_name": "Acme Inc.",
    "invoice_no": "INV-001",
    "items": [
      {
        "title": "Web Design",
        "quantity": 10,
        "unit_price": 150.00,
        "amount": null
      }
    ]
  }
}

Tip: Fields set to null in the sample data are auto-calculated by the template. Set "__auto_calculate": false in your data to supply your own values instead.

Custom Templates

For full control, write your own Nunjucks template and send it with your request. Custom templates are base64-encoded HTML strings that use Nunjucks syntax for dynamic content.

{
  "template": "PGh0bWw+PGJvZHk+SGVsbG8ge3tuYW1lfX0hPC9ib2R5PjwvaHRtbD4=",
  "data": {
    "name": "Alice"
  }
}

The template value above is the base64-encoded version of:

<html>
  <body>
    Hello {{name}}!
  </body>
</html>

Nunjucks Template Syntax

Variables

Use double curly braces to output dynamic values from your data.

<h1>Invoice #{{invoice_no}}</h1>
<p>Bill to: {{customer.name}}</p>
<p>Email: {{customer.email}}</p>

Access nested properties with dot notation:

<p>{{company.address.street}}</p>
<p>{{company.address.city}}, {{company.address.state}}</p>

Conditionals

Use {% if %}, {% elif %}, and {% else %} for conditional rendering.

{% if status == "paid" %}
  <span class="badge-green">Paid</span>
{% elif status == "pending" %}
  <span class="badge-yellow">Pending</span>
{% else %}
  <span class="badge-red">Overdue</span>
{% endif %}

You can also check for the existence of a value:

{% if discount %}
  <tr>
    <td>Discount</td>
    <td>-{{formatCurrency(locale, currency, discount)}}</td>
  </tr>
{% endif %}

Loops

Use {% for %} to iterate over arrays and objects.

Array iteration:

<table>
  <thead>
    <tr>
      <th>Item</th>
      <th>Qty</th>
      <th>Price</th>
      <th>Amount</th>
    </tr>
  </thead>
  <tbody>
    {% for item in items %}
    <tr>
      <td>{{item.title}}</td>
      <td>{{item.quantity}}</td>
      <td>{{formatCurrency(locale, currency, item.unit_price)}}</td>
      <td>{{formatCurrency(locale, currency, item.amount)}}</td>
    </tr>
    {% endfor %}
  </tbody>
</table>

Object iteration:

{% for key, value in metadata %}
  <p><strong>{{key}}:</strong> {{value}}</p>
{% endfor %}

Loop variables:

Inside a {% for %} block, Nunjucks provides special variables:

VariableDescription
loop.indexCurrent iteration (1-based)
loop.index0Current iteration (0-based)
loop.firsttrue if first iteration
loop.lasttrue if last iteration
loop.lengthTotal number of items
{% for item in items %}
  <div class="{% if loop.last %}last-item{% endif %}">
    {{loop.index}}. {{item.title}}
  </div>
{% endfor %}

Filters

Nunjucks filters transform output values. Apply them with the pipe (|) character.

Common built-in filters:

FilterExampleResult
upper`{{ “hello”upper }}`
lower`{{ “HELLO”lower }}`
capitalize`{{ “hello world”capitalize }}`
title`{{ “hello world”title }}`
trim`{{ ” hello “trim }}`
replace`{{ “foo”replace(“o”, “0”) }}`
truncate`{{ texttruncate(50) }}`
round`{{ 4.567round(2) }}`
default`{{ missingdefault(“N/A”) }}`
join`{{ itemsjoin(”, ”) }}`
length`{{ itemslength }}`
first`{{ itemsfirst }}`
last`{{ itemslast }}`
safe`{{ htmlContentsafe }}`

Chaining filters:

<p>{{ description | truncate(100) | capitalize }}</p>

Custom Functions

cloudlayer.io extends Nunjucks with two custom functions for multicultural formatting.

formatCurrency(locale, currency, amount)

Formats a numeric amount as a localized currency string.

Parameters:

ParameterTypeDescription
localestringUnicode locale identifier (e.g., "en-US", "en-GB", "de-DE", "ja-JP")
currencystringISO 4217 currency code (e.g., "USD", "EUR", "GBP", "JPY")
amountnumberThe numeric amount to format

Template usage:

<td>{{formatCurrency(locale, currency, item.unit_price)}}</td>

Examples:

LocaleCurrencyAmountOutput
en-USUSD1500.50$1,500.50
en-GBEUR150.10€150.10
de-DEEUR1234.561.234,56 €
ja-JPJPY9800¥9,800
fr-FREUR42.0042,00 €

formatDateTime(locale, dateTime)

Formats a date string into a localized date representation.

Parameters:

ParameterTypeDescription
localestringUnicode locale identifier (e.g., "en-US", "fr-FR")
dateTimestringThe string representation of the date

Template usage:

<p>Invoice Date: {{formatDateTime(locale, invoice_date)}}</p>

Examples:

LocaleInputOutput
en-US"27 March, 2020"March 27, 2020
fr-FR"27 March, 2020"27 mars 2020
de-DE"27 March, 2020"27. März 2020
ja-JP"27 March, 2020"2020年3月27日

Smart Page Breaking for PDFs

PDF templates support intelligent page breaking so that content flows naturally across multiple pages without awkward mid-element splits.

CSS Page Break Properties

Use standard CSS page break properties in your templates:

/* Always start a new page before this element */
.section-header {
  page-break-before: always;
}

/* Avoid breaking inside this element */
.invoice-item {
  page-break-inside: avoid;
}

/* Avoid a page break right after this element */
.section-title {
  page-break-after: avoid;
}

Practical Example: Multi-Page Invoice

<style>
  .invoice-table tr {
    page-break-inside: avoid;
  }

  .totals-section {
    page-break-inside: avoid;
  }

  .terms-section {
    page-break-before: always;
  }
</style>

<div class="invoice-header">
  <!-- Header content -->
</div>

<table class="invoice-table">
  {% for item in items %}
  <tr>
    <td>{{item.title}}</td>
    <td>{{item.quantity}}</td>
    <td>{{formatCurrency(locale, currency, item.unit_price)}}</td>
  </tr>
  {% endfor %}
</table>

<div class="totals-section">
  <p>Subtotal: {{formatCurrency(locale, currency, subTotal)}}</p>
  <p>Tax: {{formatCurrency(locale, currency, totalTax)}}</p>
  <p>Total: {{formatCurrency(locale, currency, amount)}}</p>
</div>

<div class="terms-section">
  <h3>Terms & Conditions</h3>
  <p>{{terms}}</p>
</div>

Tailwind CSS Support

All templates support Tailwind CSS. Link the CDN in your template’s <head> to use utility classes:

<html>
  <head>
    <script src="https://cdn.tailwindcss.com"></script>
  </head>
  <body>
    <div class="max-w-2xl mx-auto p-8">
      <h1 class="text-3xl font-bold text-gray-900">{{company_name}}</h1>
      <div class="mt-6 border-t pt-4">
        <div class="flex justify-between">
          <span class="text-gray-600">Invoice #</span>
          <span class="font-medium">{{invoice_no}}</span>
        </div>
      </div>
    </div>
  </body>
</html>

Tip: Since cloudlayer.io fetches external resources during rendering, any publicly accessible CSS framework, font, or stylesheet can be used in your templates — not just Tailwind.

Auto Calculations

Predefined templates from the gallery may support auto calculations. When enabled, the template automatically computes derived values like line item totals, subtotals, taxes, and grand totals.

How It Works

  1. Auto-calculated fields are represented as null in the sample data.
  2. When __auto_calculate is true (the default), the template computes these fields.
  3. Set __auto_calculate to false if you want to supply your own computed values.
{
  "templateId": "professional-invoice",
  "data": {
    "__auto_calculate": true,
    "tax_percentage": 6,
    "items": [
      {
        "title": "Web Design",
        "quantity": 10,
        "unit_price": 150.00,
        "amount": null
      }
    ],
    "subTotal": null,
    "totalTax": null,
    "amount": null
  }
}

In this example, amount, subTotal, totalTax, and the item-level amount are all computed automatically:

  • Item amount = quantity * unit_price = 1,500.00
  • Subtotal = sum of item amounts = 1,500.00
  • Tax = subTotal * (tax_percentage / 100) = 90.00
  • Total = subTotal + totalTax = 1,590.00

To disable auto calculation and supply your own values:

{
  "templateId": "professional-invoice",
  "data": {
    "__auto_calculate": false,
    "items": [
      {
        "title": "Web Design",
        "quantity": 10,
        "unit_price": 150.00,
        "amount": 1500.00
      }
    ],
    "subTotal": 1500.00,
    "totalTax": 90.00,
    "amount": 1590.00
  }
}

Template Data Injection

Data is injected into templates via the data property in your API request. The data object’s keys become variables available in the template.

Inline Request

Send the template and data as part of a single JSON payload:

{
  "templateId": "professional-invoice",
  "data": {
    "company_name": "Acme Inc.",
    "invoice_no": "INV-001",
    "locale": "en-US",
    "currency": "USD",
    "items": [...]
  }
}

Multipart Request

For larger templates, use multipart/form-data to send the template as a file:

curl --request POST \
  --url https://api.cloudlayer.io/v2/template/pdf \
  --header 'Content-Type: multipart/form-data' \
  --header 'x-api-key: <YOUR-API-KEY>' \
  --form 'options={"name": "my-invoice"}' \
  --form 'template=@./template.njk' \
  --form 'data=@./invoice-data.json'

With multipart requests, the template file does not need to be base64-encoded.

Practical Examples

Invoice Template

A complete invoice template using Tailwind CSS, Nunjucks loops, and the custom formatting functions.

Template (HTML):

<html>
  <head>
    <script src="https://cdn.tailwindcss.com"></script>
  </head>
  <body class="bg-white p-8 font-sans">
    <div class="max-w-3xl mx-auto">
      <!-- Header -->
      <div class="flex justify-between items-start mb-8">
        <div>
          <h1 class="text-2xl font-bold text-gray-900">{{company_name}}</h1>
          <p class="text-gray-600">{{address1}}</p>
          <p class="text-gray-600">{{city}}, {{state}} {{zip}}</p>
        </div>
        <div class="text-right">
          <h2 class="text-xl font-semibold text-blue-600">INVOICE</h2>
          <p class="text-gray-700">#{{invoice_no}}</p>
          <p class="text-gray-600">{{formatDateTime(locale, invoice_date)}}</p>
        </div>
      </div>

      <!-- Bill To -->
      <div class="mb-8 p-4 bg-gray-50 rounded">
        <h3 class="font-semibold text-gray-700 mb-2">Bill To</h3>
        <p class="font-medium">{{bill_to_fullname}}</p>
        <p class="text-gray-600">{{bill_to_address1}}</p>
        <p class="text-gray-600">
          {{bill_to_city}}, {{bill_to_state_province_region}} {{bill_to_zip_postal_code}}
        </p>
      </div>

      <!-- Line Items -->
      <table class="w-full mb-8">
        <thead>
          <tr class="border-b-2 border-gray-300">
            <th class="text-left py-2 text-gray-700">Description</th>
            <th class="text-right py-2 text-gray-700">Qty</th>
            <th class="text-right py-2 text-gray-700">Unit Price</th>
            <th class="text-right py-2 text-gray-700">Amount</th>
          </tr>
        </thead>
        <tbody>
          {% for item in items %}
          <tr class="border-b border-gray-200" style="page-break-inside: avoid;">
            <td class="py-3">
              <p class="font-medium">{{item.title}}</p>
              {% if item.description %}
              <p class="text-sm text-gray-500">{{item.description | truncate(80)}}</p>
              {% endif %}
            </td>
            <td class="text-right py-3">{{item.quantity}}</td>
            <td class="text-right py-3">
              {{formatCurrency(locale, currency, item.unit_price)}}
            </td>
            <td class="text-right py-3">
              {{formatCurrency(locale, currency, item.amount)}}
            </td>
          </tr>
          {% endfor %}
        </tbody>
      </table>

      <!-- Totals -->
      <div class="flex justify-end" style="page-break-inside: avoid;">
        <div class="w-64">
          <div class="flex justify-between py-2">
            <span class="text-gray-600">Subtotal</span>
            <span>{{formatCurrency(locale, currency, subTotal)}}</span>
          </div>
          <div class="flex justify-between py-2">
            <span class="text-gray-600">{{tax_label}} ({{tax_percentage}}%)</span>
            <span>{{formatCurrency(locale, currency, totalTax)}}</span>
          </div>
          <div class="flex justify-between py-2 border-t-2 border-gray-900 font-bold text-lg">
            <span>Total</span>
            <span>{{formatCurrency(locale, currency, amount)}}</span>
          </div>
        </div>
      </div>

      <!-- Notes -->
      {% if notes %}
      <div class="mt-8 p-4 bg-gray-50 rounded">
        <h3 class="font-semibold text-gray-700 mb-2">Notes</h3>
        <p class="text-gray-600 text-sm">{{notes}}</p>
      </div>
      {% endif %}
    </div>
  </body>
</html>

Sample data:

{
  "templateId": "professional-invoice",
  "data": {
    "__auto_calculate": true,
    "locale": "en-US",
    "currency": "USD",
    "company_name": "Acme Inc.",
    "address1": "1711 Bushnell Avenue",
    "city": "South Pasadena",
    "state": "California",
    "zip": "91030",
    "invoice_no": "INV-621",
    "invoice_date": "27 March, 2020",
    "bill_to_fullname": "Terry G. Brown",
    "bill_to_address1": "1680 Ralph Drive",
    "bill_to_city": "Cleveland",
    "bill_to_state_province_region": "OH",
    "bill_to_zip_postal_code": "44114",
    "tax_label": "Tax",
    "tax_percentage": 6,
    "items": [
      {
        "title": "Homepage Design",
        "description": "Complete redesign of the main landing page",
        "quantity": 40,
        "unit_price": 150.10,
        "amount": null
      },
      {
        "title": "Backend Development",
        "description": "API development and database setup",
        "quantity": 40,
        "unit_price": 150.00,
        "amount": null
      },
      {
        "title": "QA Testing",
        "description": "End-to-end testing and bug fixes",
        "quantity": 10,
        "unit_price": 10.00,
        "amount": null
      }
    ],
    "subTotal": null,
    "totalTax": null,
    "amount": null,
    "notes": "Payment is due within 30 days. Please include the invoice number with your payment."
  }
}

Certificate Template

A simple certificate of completion using custom styling.

Template (HTML):

<html>
  <head>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Inter&display=swap" rel="stylesheet">
  </head>
  <body class="bg-white">
    <div class="w-[800px] h-[600px] mx-auto border-8 border-double border-amber-600 p-12 flex flex-col items-center justify-center text-center">

      <p class="text-amber-600 uppercase tracking-widest text-sm mb-4">Certificate of Completion</p>

      <h1 class="text-4xl font-bold text-gray-900 mb-2" style="font-family: 'Playfair Display', serif;">
        {{recipient_name}}
      </h1>

      <div class="w-24 h-0.5 bg-amber-600 my-4"></div>

      <p class="text-gray-600 text-lg mb-2">
        has successfully completed the course
      </p>

      <h2 class="text-2xl font-semibold text-gray-800 mb-6">
        {{course_name}}
      </h2>

      <p class="text-gray-500 mb-8">
        Awarded on {{formatDateTime(locale, completion_date)}}
      </p>

      <div class="flex justify-between w-full mt-auto px-12">
        <div class="text-center">
          <div class="border-t border-gray-400 pt-2 px-8">
            <p class="font-medium">{{instructor_name}}</p>
            <p class="text-gray-500 text-sm">Instructor</p>
          </div>
        </div>
        <div class="text-center">
          <div class="border-t border-gray-400 pt-2 px-8">
            <p class="font-medium">{{organization}}</p>
            <p class="text-gray-500 text-sm">Organization</p>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

Sample data:

{
  "template": "<base64-encoded-template>",
  "data": {
    "locale": "en-US",
    "recipient_name": "Jane Smith",
    "course_name": "Advanced Web Development",
    "completion_date": "15 January, 2024",
    "instructor_name": "Dr. Alex Johnson",
    "organization": "Tech Academy"
  }
}

Request Types

Inline Request

Send everything as a single JSON payload. The template must be base64-encoded.

curl --request POST \
  --url https://api.cloudlayer.io/v2/template/pdf \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: <YOUR-API-KEY>' \
  --data '{
    "templateId": "professional-invoice",
    "data": { "company_name": "Acme Inc.", ... }
  }'

Multipart Request

Send the template and data as separate files. The template does not need to be base64-encoded.

curl --request POST \
  --url https://api.cloudlayer.io/v2/template/pdf \
  --header 'Content-Type: multipart/form-data' \
  --header 'x-api-key: <YOUR-API-KEY>' \
  --form 'options={"name": "my-document"}' \
  --form 'template=@./my-template.njk' \
  --form 'data=@./my-data.json'

Tips and Best Practices

  • Use predefined templates when possible — they are professionally designed and tested for print quality.
  • Test with small data sets first, then scale up to production data volumes.
  • Use page-break-inside: avoid on table rows and key sections to prevent awkward splits in PDFs.
  • Keep images external (hosted URLs) when possible for smaller payload sizes, or embed as data URIs for fully self-contained documents.
  • Use formatCurrency and formatDateTime instead of manual formatting to ensure correct localization across all locales.
  • Set margins to 0 when using templates (this is the default for template-based generation) and control spacing with CSS padding instead.
  • Use the safe filter ({{ htmlContent | safe }}) when you need to render raw HTML stored in your data — but only with trusted content.