Skip to content

Build a template

Build an invoice template in Studio: A4 paper with margins, a header and a “Page 1 of 2” footer on every page, page breaks where you want them, your own font and a logo. Then preview it with sample data and publish it so your application can render it.

Before you start: you need a Studio sign-in to a workspace.

  1. In the Studio sidebar, click Workspace → Editor. The editor opens in a new browser tab.
  2. In the explorer on the left, click + and choose New folder. Enter Invoices as the Folder name and click Create folder.
  3. Click ⋯ on the Invoices folder (or right-click it) and choose New entity → Template.
  4. Enter Invoice as the Template name, leave Start with on Blank template, and click Create template.

The editor opens the template’s template.html.

Replace everything in template.html with this:

template.html
<html>
<head>
<style>
body {
font-family: sans-serif;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
border-bottom: 1px solid #ccc;
padding: 4px;
text-align: left;
}
</style>
</head>
<body>
<h1>Invoice {{invoice.number}}</h1>
<p>Bill to: {{customer.name}}</p>
<table>
<thead>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
</thead>
<tbody>
{{#each lines}}
<tr>
<td>{{description}}</td>
<td>{{quantity}}</td>
<td>{{price}}</td>
</tr>
{{/each}}
</tbody>
</table>
</body>
</html>

The placeholders, such as {{customer.name}} and {{#each lines}}, use Handlebars syntax and are filled from your data. Press Ctrl+S (Cmd+S on macOS) or click Save.

Sample data is the JSON Studio uses for previews.

  1. Click ⋯ on the Invoices folder and choose New entity → Sample data.

  2. Enter sample.json as the Name and click Create entity.

  3. Click sample.json in the explorer to open it, replace {} with this, and save:

    sample.json
    {
    "invoice": { "number": "INV-1001" },
    "customer": { "name": "Marta Tamm" },
    "lines": [
    { "description": "Consulting", "quantity": 3, "price": 120 },
    { "description": "Travel", "quantity": 1, "price": 80 }
    ]
    }
  4. Click the Invoice template in the explorer to go back to it.

Click the page.json tab under the HTML and set the paper and margins in millimetres. Leave room at the top and bottom for the header and footer:

page.json
{
"pageSize": { "widthMm": 210, "heightMm": 297 },
"orientation": "portrait",
"margins": { "topMm": 32, "rightMm": 18, "bottomMm": 22, "leftMm": 18 }
}

You can also pick Paper and Margins under PDF properties in the preview toolbar, which writes the same page.json. For landscape, keep the portrait size and set "orientation": "landscape". Every setting is in page.json.

Section titled “5. Add a header and footer with page numbers”

The header is drawn in the top margin of every page, and the footer in the bottom margin.

  1. Click + at the end of the tabs under the HTML and choose Add page header. In the Page header tab (header.html), add:

    header.html
    <div style="flex: 1; padding: 10mm 18mm 0; font-size: 9pt;">
    Invoice
    {{invoice.number}}
    </div>
  2. Click + again and choose Add page footer. In the Page footer tab (footer.html), add elements with the classes pageNumber and totalPages. They are filled with the current page and the page count:

    footer.html
    <div style="flex: 1; padding: 0 18mm 8mm; font-size: 8pt; text-align: right;">
    Page <span class="pageNumber"></span> of <span class="totalPages"></span>
    </div>

The header and footer use the same data and helpers as the HTML. They span the full page width, so the side padding above lines them up with the 18 mm margins. They must fit inside the margin, which does not grow: if the header is cut off, raise topMm. Style them with inline style attributes, because a <style> element in them applies to the whole document. The other rules are in Supported CSS.

Content flows onto the next page on its own. To keep an element in one piece, or to start it on a new page, add CSS to the <style> element in template.html:

/* Never split a table row across two pages. */
tr {
break-inside: avoid;
}
/* Start the terms on a new page. */
.terms {
break-before: page;
}

For every supported break property, see Supported CSS.

Text is set in fonts bundled with Typefold until you upload and declare your own. Fonts installed on the server or loaded from a URL are never used.

  1. Click ⋯ on the Invoices folder, choose New folder and create a fonts folder.

  2. Click ⋯ on the fonts folder and choose New entity → Asset. Choose the font files (TTF, OTF or WOFF2) and click Upload assets.

  3. In the <style> element of template.html, declare each file with @font-face, one rule per weight, and use the family on body in place of sans-serif:

    @font-face {
    font-family: "Inter";
    src: url("fonts/Inter-Regular.ttf");
    font-weight: 400;
    }
    @font-face {
    font-family: "Inter";
    src: url("fonts/Inter-Bold.ttf");
    font-weight: 700;
    }
    body {
    font-family: "Inter", Arial;
    }

url() paths start from the folder of the file they are written in. From a stylesheet in a styles folder, the same font is url("../fonts/Inter-Regular.ttf").

If no font covers a character, such as Chinese text, the render fails with ASSET_INVALID. Upload a font that covers it.

  1. Create an images folder in Invoices and upload the logo (PNG, JPEG or SVG) the same way.

  2. Point to it by path, in template.html or in the header:

    <img src="./images/logo.svg" alt="Logo" style="height: 12mm" />

Images at an https:// address are not loaded, so always upload them.

  1. In the preview toolbar, set Data to sample.json.
  2. Click Update preview.

The PDF appears beside the source: A4 pages in Inter, with the logo, “Invoice INV-1001” in the header, a two-row table and “Page 1 of 1” in the footer. Click Update preview again after each change. Save the template to keep sample.json as its preview data.

Click Save, then Publish in the header. The status changes to Published v1. Your application can now render the template by its name, Invoice, with the Reports API.

A published version keeps its own copy of every file, so later edits to the template, its fonts or its images reach your application only when you publish again. See Templates.