---
title: "Invoice template for JavaScript — quario"
description: "A ready-made quario invoice report for JavaScript. Copy the definition, point it at your data, and render to HTML, PDF, Excel, or Word — no layout engine, no PDF coordinates."
url: "https://getquario.com/templates/invoice"
---

# Invoice template for JavaScript

A production invoice is fiddly. Line items wrap, totals must reconcile, currency and dates have to be right, and the layout has to print cleanly to A4. This is a ready-made quario invoice report — copy it, point it at your data, and render to HTML, PDF, Excel, or Word. No layout engine to wire up, no PDF coordinates to nudge.

## What it renders

This isn’t a screenshot. quario compiles the definition and data below, and `@quario/viewer` renders
them in your browser, right here. The PDF button hands you the same invoice as real `@quario/pdf`
bytes, no browser rendering involved. The definition asks for `sans`, `serif` and `mono`. The faces
you see are this page’s own, handed to the viewer and the PDF export as the same TrueType files.
Since `@quario/viewer` 0.4.0 the sheet is the layout’s own pages painted on a canvas. The screen
measures and draws in exactly what the PDF embeds. The DOCX button beside it hands the same
definition to `@quario/docx`, where Word paginates and resolves the faces itself:

*Preview: the page renders this definition and its sample data live, with downloads — [see it rendered](https://getquario.com/templates/invoice).*

## The template

The whole invoice is a definition — data you can version and review. Addresses, VAT numbers, dates,
the totals block and the payment line are all in here. Nothing about this document lives in code.
This is the exact file the preview above renders:

```json title="invoice.report.json"
{
  "data": "$.invoice.lines[*]",
  "aggregates": {
    "subtotal": "sum:=@.qty * @.unitPrice",
    "vat": "sum:=@.qty * @.unitPrice * @.vatRate / 100"
  },
  "style": { "family": "sans", "size": 10 },
  "header": [
    {
      "type": "split",
      "style": { "spaceAfter": 22 },
      "slots": [
        { "type": "image", "source": "=logo()", "fit": "width", "alt": "{{ $.input.invoice.seller.name }} logo", "width": 28 },
        { "type": "text", "value": "", "style": { "size": 8 } }
      ]
    },
    {
      "type": "split",
      "style": { "size": 10, "bold": false },
      "slots": [
        { "type": "text", "value": "Invoice No. {{ $.input.invoice.number }}", "width": 60,
          "style": { "family": "serif", "size": 19, "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": [{ "value": "Issued " }, { "value": "{{ $.input.invoice.issued }}" }],
          "style": { "size": 8, "color": "#989a97", "align": "right", "format": "date" } }
      ]
    },
    {
      "type": "split",
      "style": { "size": 10, "bold": false, "spaceAfter": 4 },
      "slots": [
        { "type": "text", "value": "{{ $.input.invoice.reference }}", "width": 60,
          "style": { "size": 8, "color": "#989a97" } },
        { "type": "text", "value": [{ "value": "Due " }, { "value": "{{ $.input.invoice.due }}" }],
          "style": { "size": 8, "color": "#989a97", "align": "right", "format": "date" } }
      ]
    },
    {
      "type": "split",
      "style": { "size": 10, "bold": false, "spaceBefore": 34 },
      "slots": [
        { "type": "text", "value": "From", "width": 50,
          "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } },
        { "type": "text", "value": "Bill to",
          "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } }
      ]
    },
    {
      "type": "split",
      "style": { "size": 10, "bold": false },
      "slots": [
        { "type": "text", "value": "{{ $.input.invoice.seller.name }}", "width": 50,
          "style": { "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": "{{ $.input.invoice.customer.name }}",
          "style": { "bold": true, "color": "#1e1f22" } }
      ]
    },
    {
      "type": "split",
      "style": { "size": 10, "bold": false },
      "slots": [
        { "type": "text", "value": "{{ $.input.invoice.seller.street }}", "width": 50,
          "style": { "color": "#5e6062" } },
        { "type": "text", "value": "{{ $.input.invoice.customer.street }}",
          "style": { "color": "#5e6062" } }
      ]
    },
    {
      "type": "split",
      "style": { "size": 10, "bold": false },
      "slots": [
        { "type": "text", "value": "{{ $.input.invoice.seller.city }}", "width": 50,
          "style": { "color": "#5e6062" } },
        { "type": "text", "value": "{{ $.input.invoice.customer.city }}",
          "style": { "color": "#5e6062" } }
      ]
    },
    {
      "type": "split",
      "style": { "size": 10, "bold": false },
      "slots": [
        { "type": "text", "value": "{{ $.input.invoice.seller.country }}", "width": 50,
          "style": { "color": "#5e6062" } },
        { "type": "text", "value": "{{ $.input.invoice.customer.country }}",
          "style": { "color": "#5e6062" } }
      ]
    },
    {
      "type": "split",
      "style": { "size": 10, "bold": false, "spaceAfter": 40 },
      "slots": [
        { "type": "text", "value": "VAT {{ $.input.invoice.seller.vatId }}", "width": 50,
          "style": { "size": 8, "color": "#989a97" } },
        { "type": "text", "value": "VAT {{ $.input.invoice.customer.vatId }}",
          "style": { "size": 8, "color": "#989a97" } }
      ]
    }
  ],
  "detail": {
    "header": {
      "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97",
        "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" }
    },
    "row": { "style": { "color": "#5e6062" } },
    "columns": [
      { "header": "Item", "value": "{{ @.item }}", "width": 46 },
      { "header": { "value": "Qty", "style": { "align": "right" } }, "value": "{{ @.qty }}", "width": 10,
        "style": { "align": "right" } },
      { "header": { "value": "Unit", "style": { "align": "right" } }, "value": "{{ @.unitPrice }}", "width": 16,
        "style": { "align": "right", "format": "currency" } },
      { "header": { "value": "VAT", "style": { "align": "right" } }, "value": "{{ @.vatRate / 100 }}", "width": 10,
        "style": { "align": "right", "color": "#989a97", "format": { "kind": "percent", "digits": 0 } } },
      { "header": { "value": "Amount", "style": { "align": "right" } }, "value": "{{ round(@.qty * @.unitPrice, 2) }}", "width": 18,
        "style": { "align": "right", "format": "currency" } }
    ],
    "total": {
      "style": { "color": "#5e6062", "align": "right", "paddingTop": 0, "paddingBottom": 0 },
      "rows": [
        {
          "cells": [
            { "value": "Subtotal", "span": 4 },
            { "value": "{{ round($.subtotal, 2) }}", "style": { "format": "currency" } }
          ]
        },
        {
          "cells": [
            { "value": "VAT 21%", "span": 4 },
            { "value": "{{ round($.vat, 2) }}", "style": { "format": "currency" } }
          ]
        },
        {
          "style": { "bold": true, "color": "#1e1f22", "paddingTop": 5,
            "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
          "cells": [
            { "value": "Total due", "span": 4 },
            { "value": "{{ round($.subtotal + $.vat, 2) }}", "style": { "format": "currency" } }
          ]
        }
      ]
    }
  },
  "page": {
    "footer": [
      {
        "type": "text",
        "value": "{{ $.input.invoice.terms }} to {{ $.input.invoice.seller.iban }}, quoting {{ $.input.invoice.number }}.",
        "style": { "size": 8, "color": "#989a97", "paddingTop": 10,
          "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" }
      }
    ]
  }
}
```

Four pieces of syntax carry the whole thing. `data` is a JSONPath that selects the rows the table
repeats over. `{{ … }}` interpolates a value — `@` is the current line, `$` is the report root, and
`$.input` is the data you passed in. `"sum:=@.qty * @.unitPrice"` declares an aggregate, which lands
on `$` for the totals to read. And a `split` places its slots across the line instead of stacking
them, which is how the two addresses sit side by side.

The table says each shared declaration once. The mono column labels and the rule under them are
`detail.header`’s own `style`, and the body rows’ colour is `detail.row`’s. A column states only
what it alone wants: its alignment, its `format`. A row’s box reaches its cells on every target, so
the rule under the labels is one declaration rather than one per column.

The totals are `detail.total`, a block of rows in the table itself rather than a block beneath it.
Every figure lands in the column it belongs to instead of in widths you match by eye. Each label is
one cell with a `span` covering the columns it reaches. A total row is a label and an amount, not a
line of empty spacer cells. What the rows share — the colour, the right alignment, the tighter
pitch — is the block’s own `style`, said once. A row states only what differs. The rule above
**Total due** is a `borderTop` declared on that row alone, drawn continuously across the width.

The payment line sits in `page.footer`, not the report footer. It’s drawn at the foot of every page
rather than wherever the totals happen to end. That’s what lets a one-page invoice look like a
page. Content sits at the top, the terms on the baseline, and the white space between them on
purpose. Spacing between the blocks above is `spaceBefore` / `spaceAfter` in points, declared on
the split that opens each one.

A definition declares amounts rather than formatting them. The cell interpolates the number and
`"format": "currency"` says how to show it, from the locale and currency you hand `quario()`. Every
sum a cell does itself wraps in `round(…, 2)` — a line’s `qty × unitPrice`, and subtotal plus VAT.
Exact amounts can add up to an inexact float, and where a page presents the number the workbook
stores it.

Dates work the same way. The *Issued* and *Due* lines use **styled runs** — a `value` written as a
list, `[{ "value": "Issued " }, { "value": "{{ … }}" }]`. The date stands in a run of its own. The
line’s `"format": "date"` then presents it in the instance locale’s medium form, `14 Aug 2026`
under `en-IE`, and leaves the label alone. A `format` needs one value to speak about, which is
what the split gives it. The same line as a single string, `"Issued {{ … }}"`, would render the
ISO date and `plan()` would warn. The template registers no function for any of this. A `form` of
`short`, `long` or `full` picks another shape when a document wants one.

## The data

A definition renders nothing on its own — you hand it a document. This is the sample the preview
above uses. `$.invoice.lines[*]` in the definition selects its rows, and `$.input` reads the rest:

```json title="invoice.data.json"
{
  "invoice": {
    "number": "2026-1042",
    "issued": "2026-08-14",
    "due": "2026-09-13",
    "reference": "PO 88231",
    "seller": {
      "name": "Quire Press Ltd.",
      "street": "Binnenkant 42",
      "city": "1011 BM Amsterdam",
      "country": "Netherlands",
      "vatId": "NL8123.45.678.B01",
      "iban": "NL91 ABNA 0417 1643 00"
    },
    "customer": {
      "name": "Folio & Sons",
      "street": "12 Marlborough Street",
      "city": "Dublin 1",
      "country": "Ireland",
      "vatId": "IE6388047V"
    },
    "lines": [
      { "item": "Typesetting", "qty": 12, "unitPrice": 120, "vatRate": 21 },
      { "item": "Folding & gathering", "qty": 4, "unitPrice": 80, "vatRate": 21 },
      { "item": "Case binding, quarter cloth", "qty": 250, "unitPrice": 6.4, "vatRate": 21 },
      { "item": "Foil stamping, spine", "qty": 250, "unitPrice": 1.1, "vatRate": 21 },
      { "item": "Proof copies, courier", "qty": 1, "unitPrice": 48, "vatRate": 21 }
    ],
    "terms": "Payment within 30 days"
  }
}
```

## Use it

```bash title="shell"
npm install quario @quario/html
```

```ts title="app.ts"
import { readFileSync } from 'node:fs'
import { quario } from 'quario'
import { html } from '@quario/html'
import invoice from './invoice.report.json' with { type: 'json' }

const q = quario({ locale: 'en-IE', currency: 'EUR' })   // what `format` presents through
const logo = () => readFileSync('logo.png')          // your logo, as PNG bytes
const report = q.report(invoice, { logo })

const data = await getInvoice(id)                    // your data
const page = await report.render(html(), data)       // ready to display or print
```

`logo` hands the header your logo as PNG bytes. PNG rather than SVG, because the Word and workbook renders hold a raster picture only, and a PNG reaches every target.


Show `page` in the browser for a live preview, or render the same compiled report straight to a
PDF for sending and archiving — no browser involved:

```ts title="pdf.ts"
import { writeFileSync } from 'node:fs'
import { pdf } from '@quario/pdf'

const bytes = await report.render(pdf({ page: { size: 'A4', margin: 54 } }), data)

writeFileSync('invoice.pdf', bytes)
```

See [JSON to PDF](https://getquario.com/guides/json-to-pdf.md) for both PDF routes side by side.

## Make it yours

Because the invoice is declarative, changing it means changing data. Drop the VAT column for a zero-rated customer, swap in your logo, reorder columns, localise the labels. They’re all edits to the definition, all diff-able in a pull request. The same file renders identically in Node, the browser, and at the edge, so your preview and your PDF never drift apart. And it renders **without `eval`**, so building invoices from customer data doesn’t hand you an injection problem. [Security
→](https://getquario.com/docs/diving-deeper/security.md)

## FAQ

### Can I generate the invoice as a PDF?

Yes — `@quario/pdf` renders the same definition straight to PDF bytes, with no browser anywhere. If you’d rather go through HTML and your own print CSS, that route works too. [Full example →](https://getquario.com/guides/json-to-pdf.md)

### Can I export the invoice to Excel?

Yes — `@quario/xlsx` renders the same definition to a workbook, with nothing to change. The amount columns declare `"format": "currency"` rather than baking a formatted string into the cell. The workbook gets a real number carrying a currency number format (`"EUR"#,##0.00`). The column reads as currency and your recipient can still sum it. [Full example →](https://getquario.com/guides/json-to-excel.md)

### Can I use this invoice template commercially?

The definition is yours — the report definitions, data, and output you create with quario belong to you. quario itself is free to evaluate without a time limit. Invoicing real customers is past evaluation, so that needs a license for each production deployment. Its users, agents, and renders are never counted. [Pricing →](https://getquario.com/pricing.md)

### Does it handle multiple currencies / tax lines?

This one already carries a VAT rate per line and totals it into its own row, all from the definition. A second rate is another field in your data and another aggregate. Currency is the one to think about. `"format": "currency"` presents every amount in the currency you gave `quario()`, which is right for an invoice denominated in one currency. For a document mixing several, a cell names its own — `"currency": "=@.currency"` beside the `format` — and the amount stays a number. That row’s cell reads `US$` on the page and carries `"USD"#,##0.00` in the workbook rather than arriving as text.

### Where are the other templates?

[Browse the template gallery →](https://getquario.com/templates.md) — quotes, receipts, purchase orders, and more.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://getquario.com/"},{"@type":"ListItem","position":2,"name":"Templates","item":"https://getquario.com/templates"},{"@type":"ListItem","position":3,"name":"Invoice","item":"https://getquario.com/templates/invoice"}]}
```
