JSON to PDF Template Generator

Fill an HTML template with {{placeholders}}, loops and totals from JSON, preview it live, and download a PDF or the HTML.

Start from a template
Template syntax
{{name}}, {{customer.city}}Insert a value. HTML characters are escaped, so data can never break the layout.
{{{notes_html}}}Insert a value as raw HTML (scripts are removed and never run).
{{#each items}} โ€ฆ {{/each}}Repeat for every item in a list. Inside, use the item's fields, {{this}}, {{@number}} (1, 2, 3) or {{@index}} (0, 1, 2).
{{#if paid}} โ€ฆ {{else}} โ€ฆ {{/if}}Show a block only when a value is present and not false, 0 or empty. {{#unless}} is the opposite.
{{price|money:"$"}}Format a number as money with 2 decimals. The symbol can also come from your data: money:currency.
{{qty|times:price}}Math: times, divide, plus, minus, percent (x% of), add_percent (add tax), round.
{{items|sum:"qty*price"}}Add up a field across a list, or the product of two fields. {{items|count}} counts the list.
{{status|eq:"paid"}}True when equal, for use in {{#if}}. Text pipes: upper, lower, title, default:"n/a", number.
{{! a note }}A comment. It is left out of the output.
Page size
Orientation

Live preview

The preview updates as you type and uses your browser. The PDF is drawn by our server's PDF engine, which does not support flexbox or grid, so the starter templates use tables for layout.

Templates are filled in your browser. Only the finished HTML goes to our server to build the PDF, and it is not stored. Your edits are saved in this browser.

In short

Pick a starter template (invoice, receipt, certificate, shipping label or letter) or write your own HTML with {{placeholders}} and {{#each}} loops. Paste your JSON data, check the live preview and the error hints, then click Download PDF. The template is filled in your browser and the PDF is drawn by our server.

On this page
  1. How to make a PDF from JSON
  2. The starter templates
  3. Template syntax
  4. Worked example: the invoice totals
  5. Fixing JSON and template errors
  6. Designing your own template
  7. Where the JSON comes from
  8. PDF, HTML or browser print
  9. Limitations

When the same document goes out again and again with different details, such as invoices, packing slips, certificates or form letters, the layout should be written once and the data should change. This generator works that way. You keep an HTML template with placeholders like {{customer.name}}, paste the data as JSON, and get a finished PDF. Change the JSON and the document changes with it, with no copy and paste into a word processor.

The template language is a small, safe version of Mustache and Handlebars, the syntax most developers already know. Values are HTML-escaped by default, loops repeat rows for every item, and filters format money and add up totals. The template is filled in your browser; only the finished HTML goes to our server, where the same engine as our HTML to PDF converter draws the PDF.

How to make a PDF from JSON

  1. Choose a starter under Start from a template: Invoice, Receipt, Certificate, Shipping label, Letter or Blank. Each one loads a template, sample data and page settings that suit it.
  2. Edit the Template box. It is ordinary HTML and CSS with placeholders in double curly braces.
  3. Replace the sample in the JSON data box with your own data, or click Open a .json file. Format JSON tidies the indentation.
  4. Read the two status lines. Under the data box you see Valid JSON or the exact line and column of an error. Under the template box you see Template OK, a syntax error with its line number, or a list of placeholders that were not found in your data.
  5. Check the Live preview. It redraws as you type, at the printable width of the page size and margins you chose.
  6. Pick a page size (A4, Letter, Legal or 4 x 6 in), orientation and margins, and a file name if you do not want the one taken from the template's <title>.
  7. Click Download PDF, or Download HTML to keep the filled page, or Print with my browser to save it through your browser's print dialog.

Your template and data are saved in this browser for each starter, so you can close the tab and continue later. Reset this template brings back the original.

The starter templates

StarterPage setup it loadsWhat the data holds
InvoicePortrait, 15 mm marginsSeller, customer, invoice number and dates, line items with quantity and price, discount, tax rate, notes; subtotal, tax and total are calculated
ReceiptPortrait, 15 mm margins, narrow centered slipStore, receipt number, date, items, tax rate and payment method; totals are calculated
CertificateLandscape, 10 mm marginsOrganization, recipient, course, hours, date, signers and a certificate ID
Shipping label4 x 6 in, 5 mm marginsService, sender and recipient addresses, order number, weight, item list and tracking number
LetterPortrait, 20 mm marginsLetterhead, date, recipient, subject, a list of paragraphs and the sign-off
BlankPortrait, 15 mm marginsA title and a short list, as a clean starting point

The page size starts as Letter if your browser language is set to a region that uses Letter paper, such as the US or Canada, and A4 elsewhere; the label always starts at 4 x 6 in. All starters lay out columns with tables, because the PDF engine does not support flexbox or grid, so what you see in the preview is what you get in the PDF.

Template syntax

The full reference is under Template syntax in the tool. These are the parts you will use most.

Values

{{name}} inserts a value. Dots reach into nested objects: {{customer.address}}. Characters such as < and & are escaped, so a customer called "Smith & Sons" prints correctly and data can never inject markup into your layout. A value that is missing prints as blank, and the template status line lists it so you can spot a typo.

Triple braces insert raw HTML: {{{notes_html}}}. Use them for data that already contains formatting, such as a bold word or a line break. Script tags and event handlers are removed from the finished HTML in the preview, in browser printing and on the server, so raw HTML can style the page but cannot run code.

Loops and conditions

{{#each items}} ... {{/each}} repeats its content for every item in a list. Inside the loop, the item's own fields are available by name, {{this}} is the whole item (useful for lists of plain text), {{@number}} counts from 1 and {{@index}} from 0. Fields from outside the loop are still found, so {{currency}} works inside it; {{../name}} reaches one level out when the item has a field with the same name. An {{else}} inside a loop shows when the list is empty.

{{#if paid}} ... {{else}} ... {{/if}} shows a block when the value is present and not false, 0, an empty string or an empty list. {{#unless}} is the reverse, and {{#with customer}} lets you write {{name}} instead of {{customer.name}} inside the block. {{! comment }} is left out of the output.

Filters for money and totals

A filter follows a pipe and can take arguments after colons. Arguments can be quoted text, numbers or field names.

WriteWith this dataPrints
{{price|money:"$"}}price: 1234.5$1,234.50
{{qty|times:price|money:currency}}qty: 12, price: 18.5, currency: "$"$222.00
{{items|sum:"qty*price"}}two items, 2 x 1.10 and 3 x 0.704.3
{{items|count}}a list of 2 items2
{{subtotal|add_percent:tax_rate}}subtotal: 100, tax_rate: 8.25108.25
{{status|upper}}status: "paid"PAID
{{phone|default:"n/a"}}phone missingn/a

The math filters are times, divide, plus, minus, percent (x percent of the value), add_percent and round. The text filters are upper, lower, title, default, join and number. For conditions there are eq, ne, gt and lt, so {{#if status|eq:"paid"}} shows a PAID stamp only on paid invoices. Money is rounded to the cent when it is printed, not before, so totals match what a spreadsheet would give.

Worked example: the invoice totals

The invoice starter has three lines: 12 linen throws at $18.50, 24 mugs at $6.25 and 18 candles at $7.40, with a $25 discount and 5.5% tax. The template never stores a total. It calculates them:

  • Each line: {{qty|times:price|money:currency}} gives $222.00, $150.00 and $133.20.
  • Subtotal: {{items | sum:"qty*price" | money:currency}} gives $505.20.
  • Tax: subtotal minus discount, then 5.5 percent of it: {{items | sum:"qty*price" | minus:discount | percent:tax_rate | money:currency}} gives $480.20 x 5.5% = $26.41.
  • Total due: {{items | sum:"qty*price" | minus:discount | add_percent:tax_rate | money:currency}} gives $506.61.

Add a fourth item to the JSON and every figure updates in the preview before you download. Change "currency": "$" to "โ‚ฌ" and every amount changes symbol. If you sell on Shopify and only need a standard invoice, the Shopify invoice generator is a form with no template to edit.

Fixing JSON and template errors

JSON is strict, and most errors come from a handful of habits carried over from JavaScript. The status line under the data box names the line and column and shows the line with a marker under the problem.

MessageUsual causeFix
A property name needs double quotes, or there is an extra comma{'name': 1} or a comma before }Use "double quotes" and remove the last comma in a list or object
A comma is missing between two valuesTwo lines without a comma between themAdd a comma at the end of the line above the marker
The JSON ends too earlyA missing } or ]Count your brackets; Format JSON helps once it parses
A text value contains a real line breakPressing Enter inside quotesWrite \n inside the quotes instead

Template errors are reported the same way, with a line number: a {{#each}} that is never closed, a {{/if}} that closes the wrong block, or a filter name that does not exist. While there is an error, the preview keeps showing the last version that worked, so you do not lose your place.

Designing your own template

The fastest way to a custom template is to start from the closest starter and change it, because the starters already solve the print problems. If you write one from scratch, these rules keep the preview and the PDF in step:

  1. Build side-by-side areas with tables. A two-cell table with width: 100% gives you a logo on the left and invoice details on the right on every page size.
  2. Put all CSS in a <style> block in the template. Placeholders work inside CSS too, so color: {{brand_color | default:"#0f3d5e"}} lets each document carry its own brand color.
  3. Give every field a sensible fallback with default, so a missing phone number prints n/a instead of an empty gap.
  4. Wrap optional parts in {{#if}}. The invoice starter hides the discount row and the notes box when the data has none.
  5. Keep one row per item in loops. Because a table row cannot split across pages, a loop of short rows flows onto page two cleanly, while one giant row cannot.
  6. Set the <title> from your data, for example Invoice {{invoice.number}}. It becomes the file name, which saves renaming every download.

Test the template with awkward data before you rely on it: a long company name, an address with an apostrophe, twenty line items and a list with no items at all. If all four look right in the preview, they will look right in the PDF.

Where the JSON comes from

Most systems can hand you JSON. Store and marketplace APIs return orders as JSON with a list of line items, form tools and spreadsheets can export rows as JSON, and developers can print it from their own code. You do not need to change the data to fit the starter: rename the placeholders in the template to match your field names instead. For example, if your order data has a line_items list whose items have title, quantity and price, change the loop to {{#each line_items}} and the cells to {{title}}, {{quantity}} and {{quantity|times:price|money:"$"}}.

If your catalog data lives in a store you manage with AM Jarvis, the Product Importer keeps product titles, variants and prices consistent, which makes the documents you generate from that data consistent too.

PDF, HTML or browser print

  • Download PDF sends the filled HTML to our server, which draws a vector PDF with selectable text. It counts toward the daily free limit shared with the HTML to PDF converter.
  • Download HTML saves the filled page with its page-size rule, entirely in your browser. Open it later, email it, or keep it as a record of exactly what was sent.
  • Print with my browser opens your browser's print dialog for the filled page, with scripts disabled. Choose Save as PDF. Use it when you have written a layout with flexbox or grid, which the server engine does not support.

Limitations

  • One document per download. There is no batch mode that makes a PDF for every record in a list; for that, loop over the records in your own code and post each one.
  • The filled HTML must stay under 2 MB and about 8,000 elements, the same limits as the HTML to PDF converter.
  • Filters cover formatting and simple math. There are no custom functions, partials or date formatting, so format dates in your data the way you want them printed.
  • Images must be https addresses or data: URIs. Inline <svg> markup does not render in the PDF; link the SVG as an image instead.
  • The shipping label prints the tracking number as text. It does not generate a carrier barcode; make one with our barcode generator and insert it as an image.

Frequently asked questions

What template syntax does the generator use?

A safe subset of Mustache and Handlebars: {{field}} for escaped values, {{{field}}} for raw HTML, {{#each}}, {{#if}}, {{#unless}}, {{#with}} and {{else}} blocks, plus filters after a pipe for money, math, totals and text. There are no partials or custom helpers.

Can the template calculate invoice totals?

Yes. {{qty|times:price}} gives a line total, {{items|sum:"qty*price"}} adds up a list, and minus, percent and add_percent handle discounts and tax. Add |money:"$" at the end to print two decimals with thousands separators.

Is my data uploaded?

The template is filled in your browser, and the preview, HTML download and browser print never leave it. Only Download PDF sends the finished HTML to our server to draw the PDF, and it is not stored. Your template and JSON are saved in your own browser so you can come back to them.

Why does my JSON show an error?

JSON needs double quotes around names and text, no comma after the last item, and no comments. The status line gives the line and column and marks the spot. Fix it there, then use Format JSON to tidy the layout. The preview keeps the last good version while you edit.

Can I use my own HTML design?

Yes. Choose Blank or edit any starter. Any HTML and CSS works in the live preview. For the PDF, use tables for side-by-side columns, because the PDF engine does not support flexbox or grid, or use Print with my browser for those layouts.

Can I make a PDF for every row in my data at once?

Not in this tool. It makes one document per download. For a handful of documents, paste each record and download in turn. For hundreds, generate the HTML in your own code with the same template and render it there.

What page sizes are available?

A4, Letter, Legal and 4 x 6 inches for shipping labels, each in portrait or landscape, with margins from none to 25 mm. The size and margins you choose override any @page rule in the template.