---
title: "Receipt template for JavaScript — quario"
description: "A ready-made quario receipt report for JavaScript. An 80 mm till roll from the same kind of definition as an A4 invoice — render to HTML, PDF, Excel, or Word."
url: "https://getquario.com/templates/receipt"
---

# Receipt template for JavaScript

A till receipt is 80 millimetres wide and as long as it needs to be. That sounds like a different
problem from an A4 invoice, and it isn’t: this is the same kind of definition, rendered onto
different paper. Nothing in the file below mentions 80 millimetres. The paper is the host’s to
choose, so one definition prints to a roll, a sheet, or a screen.

## What it renders

This isn’t a screenshot. quario compiles the definition and data below, and `@quario/viewer` renders
them in your browser. That’s an 80 mm roll, because it’s what this page’s host asked for. The PDF
button hands you the same receipt as real `@quario/pdf` bytes, one page, 226.8 × 453.5 points. 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/receipt).*

## The template

Everything the receipt says is in here, from the merchant block and the line table to the two VAT
bands and the card line. The document sets itself in `mono` at 7 point in a single declaration at
the top. That’s the way a receipt printer sets everything in one face. The table and footer below
inherit it without repeating it.

```json title="receipt.report.json"
{
  "data": "$.receipt.lines[*]",
  "aggregates": {
    "subtotal": "sum:=@.qty * @.unitPrice",
    "vatLow": "sum:=@.vatRate == 9 ? @.qty * @.unitPrice * 0.09 : 0",
    "vatHigh": "sum:=@.vatRate == 21 ? @.qty * @.unitPrice * 0.21 : 0"
  },
  "style": { "family": "mono", "size": 7 },
  "header": [
    {
      "type": "split",
      "style": { "spaceAfter": 6 },
      "slots": [
        { "type": "text", "value": "", "width": 43, "style": { "size": 7 } },
        { "type": "image", "source": "=logo('mark')", "fit": "width", "alt": "{{ $.input.receipt.merchant.name }} logo", "width": 14 },
        { "type": "text", "value": "", "style": { "size": 7 } }
      ]
    },
    { "type": "text", "value": "{{ $.input.receipt.merchant.name }}",
      "style": { "size": 11, "bold": true, "color": "#1e1f22", "align": "center" } },
    { "type": "text", "value": "{{ $.input.receipt.merchant.street }}",
      "style": { "size": 7, "bold": false, "color": "#5e6062", "align": "center" } },
    { "type": "text", "value": "{{ $.input.receipt.merchant.city }}",
      "style": { "size": 7, "bold": false, "color": "#5e6062", "align": "center" } },
    { "type": "text", "value": "VAT {{ $.input.receipt.merchant.vatId }}",
      "style": { "size": 6, "bold": false, "color": "#989a97", "align": "center" } },
    {
      "type": "split",
      "style": { "spaceBefore": 8, "spaceAfter": 8 },
      "slots": [
        { "type": "text", "value": "{{ $.input.receipt.number }}", "width": 50,
          "style": { "size": 7, "bold": false, "color": "#5e6062" } },
        { "type": "text", "value": [{ "value": "{{ $.input.receipt.issued }}" }, { "value": " {{ $.input.receipt.time }}" }],
          "style": { "size": 7, "bold": false, "color": "#5e6062", "align": "right", "format": "date" } }
      ]
    }
  ],
  "detail": {
    "header": {
      "style": { "size": 6, "uppercase": true, "color": "#989a97",
        "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" }
    },
    "row": { "style": { "color": "#5e6062" } },
    "columns": [
      { "header": "Item", "value": "{{ @.item }}", "width": 32 },
      { "header": { "value": "Qty", "style": { "align": "right" } }, "value": "{{ @.qty }}", "width": 12,
        "style": { "align": "right" } },
      { "header": { "value": "Unit", "style": { "align": "right" } }, "value": "{{ @.unitPrice }}", "width": 20,
        "style": { "align": "right", "format": "currency" } },
      { "header": { "value": "VAT", "style": { "align": "right" } }, "value": "{{ @.vatRate / 100 }}", "width": 13,
        "style": { "align": "right", "color": "#989a97", "format": { "kind": "percent", "digits": 0 } } },
      { "header": { "value": "Amount", "style": { "align": "right" } }, "value": "{{ round(@.qty * @.unitPrice, 2) }}", "width": 23,
        "style": { "align": "right", "format": "currency" } }
    ],
    "total": {
      "style": { "color": "#989a97", "align": "right", "paddingTop": 0, "paddingBottom": 0 },
      "rows": [
        {
          "style": { "color": "#5e6062", "paddingTop": 7 },
          "cells": [
            { "value": "Subtotal", "span": 4 },
            { "value": "{{ round($.subtotal, 2) }}", "style": { "format": "currency" } }
          ]
        },
        {
          "visible": "=$.vatLow != 0",
          "cells": [
            { "value": "VAT 9%", "span": 4 },
            { "value": "{{ round($.vatLow, 2) }}", "style": { "format": "currency" } }
          ]
        },
        {
          "visible": "=$.vatHigh != 0",
          "cells": [
            { "value": "VAT 21%", "span": 4 },
            { "value": "{{ round($.vatHigh, 2) }}", "style": { "format": "currency" } }
          ]
        },
        {
          "style": { "size": 9, "bold": true, "color": "#1e1f22" },
          "cells": [
            { "value": "Total", "span": 4, "style": { "uppercase": true } },
            { "value": "{{ round($.subtotal + $.vatLow + $.vatHigh, 2) }}", "style": { "format": "currency" } }
          ]
        }
      ]
    }
  },
  "footer": [
    {
      "type": "split",
      "style": { "spaceBefore": 8 },
      "slots": [
        { "type": "text", "value": "{{ $.input.receipt.payment.method }} ••••{{ $.input.receipt.payment.last4 }}",
          "width": 60, "style": { "color": "#5e6062" } },
        { "type": "text", "value": "{{ round($.subtotal + $.vatLow + $.vatHigh, 2) }}",
          "style": { "color": "#5e6062", "align": "right", "format": "currency" } }
      ]
    },
    { "type": "text", "value": "Auth {{ $.input.receipt.payment.auth }}",
      "style": { "size": 6, "color": "#989a97" } },
    { "type": "text", "value": "Thank you",
      "style": { "size": 8, "color": "#1e1f22", "align": "center", "spaceBefore": 8 } },
    { "type": "text", "value": "{{ $.input.receipt.note }}",
      "style": { "size": 6, "color": "#989a97", "align": "center" } }
  ]
}
```

One detail is worth copying. Every item in the **report header** states its own `size`. That band
carries a headline default — bold at 14 point — which sits above the document default. A header
item that declares no size comes out headline-sized. Items in the table and the footer have no such
default and simply inherit the 7 point set at the top.

Two things here that the [invoice](https://getquario.com/templates/invoice.md) doesn’t do. The VAT bands are **conditional
aggregates**. `"sum:=@.vatRate == 9 ? @.qty * @.unitPrice * 0.09 : 0"` sums only the rows that
match. A receipt mixing 9% books with 21% printing totals each band separately, without grouping
or a second pass over the data. And each band declares `"visible": "=$.vatLow != 0"`, so a receipt
with nothing in that band simply doesn’t print the line.

The total wraps its sum in `round(…, 2)`. Subtotal plus the two bands computes to
`83.75999999999999` for the sample basket — exact amounts, inexact sum. `"format": "currency"`
presents that as €83.76 on the roll. The workbook stores the number the cell holds, so the
definition rounds it first.

## The data

This is the sample the preview renders. `$.receipt.lines[*]` selects the rows the table repeats
over. `$.input` reads the merchant, the number, and the payment:

```json title="receipt.data.json"
{
  "receipt": {
    "number": "R-2026-0884",
    "issued": "2026-08-21",
    "time": "14:32",
    "merchant": {
      "name": "Quire Press Ltd.",
      "street": "Binnenkant 42",
      "city": "1011 BM Amsterdam",
      "vatId": "NL8123.45.678.B01"
    },
    "lines": [
      { "item": "Marbled endpapers", "qty": 4, "unitPrice": 3.5, "vatRate": 21 },
      { "item": "Bookcloth, quarter", "qty": 1, "unitPrice": 18, "vatRate": 21 },
      { "item": "Proof copy, A4", "qty": 2, "unitPrice": 6, "vatRate": 21 },
      { "item": "Type specimen book", "qty": 1, "unitPrice": 28, "vatRate": 9 }
    ],
    "payment": { "method": "Maestro", "last4": "4417", "auth": "0043821" },
    "note": "Returns within 14 days with this receipt"
  }
}
```

The header line carries both as two styled runs. The date sits in a run of its own, which the
line’s `"format": "date"` presents as `21 Aug 2026`. The time sits in the run beside it, printed
exactly as the data supplied it. That’s deliberate: a till knows the local time, and the receipt
just prints what it receives.

## 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 receipt from './receipt.report.json' with { type: 'json' }

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

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

`logo` hands the header your mark as PNG bytes, in shades of grey, the way a thermal printer prints it. The definition asks for `logo('mark')`, so one function can serve a mark here and a full logo elsewhere.


The roll is a render option, not part of the document. `size` takes a `[width, height]` in points —
80 mm is 226.8 of them — and the margin comes down to what a receipt printer actually leaves:

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

const bytes = await report.render(pdf({ page: { size: [226.8, 453.5], margin: 14 } }), data)

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

Swap that one object for `{ size: 'A4', margin: 54 }` and the same definition prints a full-sheet
receipt instead. See [JSON to PDF](https://getquario.com/guides/json-to-pdf.md) for both PDF routes side by side.

## Make it yours

The document says what belongs on the paper. The host says how big the paper is. So the edits you’d
expect to be layout work are edits to data. Change the roll width for a 58 mm printer. Add a logo
above the merchant name. Drop the VAT bands for a jurisdiction that doesn’t need them, and localise
the labels. All of it diffs in a pull request, and the same definition renders identically in Node,
the browser, and at the edge. It renders **without `eval`**, so a customer’s own line items never
become code. [Security →](https://getquario.com/docs/diving-deeper/security.md)

## FAQ

### Can I print this to a thermal receipt printer?

The PDF route gives you an 80 mm page you can send to any printer that takes one. For an ESC/POS
printer that wants raw commands rather than a page, render the same definition through your own
target. The engine hands a target a stream of bands, items, and resolved styles, not markup.

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

Yes — `@quario/pdf` renders this definition straight to PDF bytes, no browser anywhere. The page
geometry above is the only thing you change to move between a roll and a sheet.
[Full example →](https://getquario.com/guides/json-to-pdf.md)

### Can I export the receipt to Excel?

Yes — `@quario/xlsx` renders the same definition to a workbook, and it simply ignores the roll
geometry. A worksheet has no page to be 80 mm wide. Nothing to change if you do. The money
columns declare `"format": "currency"` rather than baking a formatted string into the cell, so
the amounts arrive as real numbers.
[Full example →](https://getquario.com/guides/json-to-excel.md)

### Can I use this receipt template commercially?

The definition is yours — the report definitions, data, and output you create with quario belong to
you. quario is free to evaluate with no time limit. Taking real money at a real counter 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)

### What if a sale is only one VAT rate?

Then one band prints and the other doesn’t — that’s what `visible` on each band is for. Adding a
third rate is one more aggregate and one more line. Nothing about the table changes.

### Where are the other templates?

[Browse the template gallery →](https://getquario.com/templates.md) — invoices, purchase orders, statements, 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":"Receipt","item":"https://getquario.com/templates/receipt"}]}
```
