---
title: "Summary report template for JavaScript — quario"
description: "A ready-made quario summary report for JavaScript. Headline figures, one section per region with its subtotal and share, page numbers on every page. Copy the definition, point it at your ledger, and render to HTML, PDF, Excel, or Word."
url: "https://getquario.com/templates/summary-report"
---

# Summary report template for JavaScript

A summary report answers one question at the top and shows its working underneath. Here the
question is how the quarter went. Revenue, orders, the average and the largest, then every order by
region with a subtotal per section and the region’s share of the whole. What makes it awkward to
build is that the sections come from the data. You don’t know how many there are until you run it,
and a quarter of orders runs past a page. This is a ready-made quario summary report — copy it,
point it at your ledger, and render to HTML, PDF, Excel, or Word.

## 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 report as real `@quario/pdf`
bytes, no browser rendering involved. The sheet is the layout’s own pages painted on a canvas. The
page break, the continuation header, and the page numbers you see are the ones the PDF carries.
`@quario/html` renders an unpaginated fragment and ignores page bands, so an HTML render has the
sections and the totals but no page numbers. The DOCX button hands the same definition to
`@quario/docx`, where Word paginates and resolves the faces itself. The page numbers survive
that trip as fields Word recomputes:

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

## The template

The whole report is a definition — data you can version and review. The headline figures, the
sections, the subtotals, the grand total and the page furniture are all in here. Nothing about this
document lives in code. This is the exact file the preview above renders:

```json title="summary-report.report.json"
{
  "data": "$.sales.orders[*]",
  "sort": [{ "by": "=@.region" }, { "by": "=@.date" }],
  "aggregates": {
    "revenue": "sum:=@.amount",
    "orders": "count",
    "average": "avg:=@.amount",
    "largest": "max:=@.amount"
  },
  "style": { "family": "sans", "size": 10 },
  "header": [
    {
      "type": "split",
      "slots": [
        { "type": "text", "value": "Sales summary, {{ $.input.sales.period.label }}", "width": 60,
          "style": { "family": "serif", "size": 19, "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": [{ "value": "{{ $.input.sales.period.from }}" }, { "value": " to " }, { "value": "{{ $.input.sales.period.to }}" }],
          "style": { "size": 8, "bold": false, "color": "#989a97", "align": "right", "format": "date" } }
      ]
    },
    {
      "type": "split",
      "slots": [
        { "type": "text", "value": "{{ $.input.sales.company }}", "width": 60,
          "style": { "size": 8, "bold": false, "color": "#989a97" } },
        { "type": "text", "value": [{ "value": "Prepared " }, { "value": "{{ $.input.sales.prepared }}" }],
          "style": { "size": 8, "bold": false, "color": "#989a97", "align": "right", "format": "date" } }
      ]
    },
    {
      "type": "split",
      "style": { "spaceBefore": 28 },
      "slots": [
        { "type": "text", "value": "Revenue", "width": 25,
          "style": { "family": "mono", "size": 7, "bold": false, "uppercase": true, "color": "#989a97" } },
        { "type": "text", "value": "Orders", "width": 25,
          "style": { "family": "mono", "size": 7, "bold": false, "uppercase": true, "color": "#989a97" } },
        { "type": "text", "value": "Average order", "width": 25,
          "style": { "family": "mono", "size": 7, "bold": false, "uppercase": true, "color": "#989a97" } },
        { "type": "text", "value": "Largest order",
          "style": { "family": "mono", "size": 7, "bold": false, "uppercase": true, "color": "#989a97" } }
      ]
    },
    {
      "type": "split",
      "style": { "spaceAfter": 20 },
      "slots": [
        { "type": "text", "value": "{{ round($.revenue, 2) }}", "width": 25,
          "style": { "family": "serif", "size": 16, "bold": false, "color": "#1e1f22", "format": { "kind": "currency", "digits": 0 } } },
        { "type": "text", "value": "{{ $.orders }}", "width": 25,
          "style": { "family": "serif", "size": 16, "bold": false, "color": "#1e1f22" } },
        { "type": "text", "value": "{{ round($.average, 2) }}", "width": 25,
          "style": { "family": "serif", "size": 16, "bold": false, "color": "#1e1f22", "format": { "kind": "currency", "digits": 0 } } },
        { "type": "text", "value": "{{ $.largest }}",
          "style": { "family": "serif", "size": 16, "bold": false, "color": "#1e1f22", "format": { "kind": "currency", "digits": 0 } } }
      ]
    }
  ],
  "groups": [
    {
      "name": "region",
      "by": "=@.region",
      "aggregates": { "subtotal": "sum:=@.amount", "orders": "count" },
      "header": [
        {
          "type": "split",
          "style": { "spaceBefore": 16, "spaceAfter": 2 },
          "slots": [
            { "type": "text", "value": "{{ region.key }}", "width": 50,
              "style": { "size": 11, "bold": true, "color": "#1e1f22" } },
            { "type": "text", "value": "{{ region.orders }} orders · {{ round(region.subtotal / $.revenue * 100, 1) }}% of revenue",
              "style": { "size": 8, "bold": false, "color": "#989a97", "align": "right" } }
          ]
        }
      ]
    }
  ],
  "detail": {
    "header": {
      "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97",
        "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" }
    },
    "row": { "style": { "color": "#5e6062" } },
    "columns": [
      { "header": "Date", "value": "{{ @.date }}", "width": 16, "style": { "format": "date" } },
      { "header": "Order", "value": "{{ @.number }}", "width": 16, "style": { "family": "mono", "size": 8, "color": "#989a97" } },
      { "header": "Customer", "value": "{{ @.customer }}", "width": 46 },
      { "header": { "value": "Amount", "style": { "align": "right" } }, "value": "{{ @.amount }}", "width": 22, "style": { "align": "right", "format": "currency" } }
    ],
    "total": {
      "style": { "color": "#1e1f22", "align": "right", "paddingTop": 3, "paddingBottom": 0,
        "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#989a97" },
      "rows": [
        {
          "cells": [
            { "value": "Subtotal {{ region.key }}", "span": 3 },
            { "value": "{{ round(region.subtotal, 2) }}", "style": { "format": "currency" } }
          ]
        }
      ]
    }
  },
  "footer": [
    {
      "type": "split",
      "style": { "spaceBefore": 20, "paddingTop": 6,
        "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
      "slots": [
        { "type": "text", "value": "Total revenue, {{ $.orders }} orders", "width": 70,
          "style": { "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": "{{ round($.revenue, 2) }}",
          "style": { "bold": true, "color": "#1e1f22", "align": "right", "format": "currency" } }
      ]
    },
    { "type": "text", "value": "Amounts exclude VAT. Source: {{ $.input.sales.source }}.",
      "style": { "size": 8, "color": "#989a97", "spaceBefore": 8 } }
  ],
  "page": {
    "header": [
      {
        "type": "text",
        "value": "Sales summary, {{ $.input.sales.period.label }} · continued",
        "visible": "=page.number > 1",
        "style": { "size": 8, "color": "#989a97", "paddingBottom": 6, "spaceAfter": 18,
          "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" }
      }
    ],
    "footer": [
      {
        "type": "split",
        "style": { "paddingTop": 10,
          "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
        "slots": [
          { "type": "text", "value": "{{ $.input.sales.company }} · internal", "width": 70,
            "style": { "size": 8, "color": "#989a97" } },
          { "type": "text", "value": "Page {{ page.number }} of {{ page.total }}",
            "style": { "size": 8, "color": "#989a97", "align": "right" } }
        ]
      }
    ]
  }
}
```

The headline figures are four report aggregates — `sum`, `count`, `avg` and `max` over the same
rows the table lists — read from `$` in the header. Each sits in a slot of its own so its `format`
applies to that number alone. `{ "kind": "currency", "digits": 0 }` presents the money at headline
size without cents. The order count carries no format and prints as the integer it is.

The sections are a **group** on `=@.region`. Each instance carries its own aggregates —
`region.subtotal` and `region.orders`. Its header reads them beside `region.key`, the region
name, and beside the report’s `$.revenue`. That’s how the share is one expression:
`round(region.subtotal / $.revenue * 100, 1)`. A group instance doesn’t know it’s the second of
three. It knows its own rows and the document’s totals, and that’s enough.

The subtotal is `detail.total`. Inside a group the table repeats per instance, so its total block
renders once per section reading `region.subtotal`. That’s a subtotal row in the section’s own
columns rather than a split aligned by eye. The grand total is the report `footer`, which comes
after the last section and reads `$.revenue` and `$.orders`. Both wrap in `round(…, 2)`. A sum of
exact amounts can land on an inexact float, and where a page presents the number the workbook
stores it.

The page bands are what make it a report rather than a list. `page.header` declares `"visible":
"=page.number > 1"`, so the _continued_ line appears from the second page on, and `page.footer`
carries `Page {{ page.number }} of {{ page.total }}`. The column labels repeat on every page
the table runs onto, and nothing asks them to.

## The data

A definition renders nothing on its own — you hand it a document. This is the sample the preview
above uses. `$.sales.orders[*]` in the definition selects its rows and `$.input` reads the period
and the company. The rows arrive in date order and the report’s `sort` puts them by region first:

```json title="summary-report.data.json"
{
  "sales": {
    "company": "Quire Press Ltd.",
    "period": { "label": "Q3 2026", "from": "2026-07-01", "to": "2026-09-30" },
    "prepared": "2026-10-02",
    "source": "sales ledger, invoices issued in the period",
    "orders": [
      {"date": "2026-07-02", "number": "2026-1029", "customer": "Folio & Sons", "region": "Ireland & UK", "amount": 1600},
      {"date": "2026-07-03", "number": "2026-1030", "customer": "Studio Halfweg", "region": "Benelux", "amount": 500},
      {"date": "2026-07-08", "number": "2026-1031", "customer": "Boekhandel Van Rossem", "region": "Benelux", "amount": 240},
      {"date": "2026-07-10", "number": "2026-1032", "customer": "Museum of Print, Antwerp", "region": "Benelux", "amount": 3200},
      {"date": "2026-07-15", "number": "2026-1033", "customer": "Folio & Sons", "region": "Ireland & UK", "amount": 40},
      {"date": "2026-07-17", "number": "2026-1034", "customer": "Lindqvist Förlag", "region": "Nordics", "amount": 390},
      {"date": "2026-07-22", "number": "2026-1035", "customer": "Hatch & Lane, Bristol", "region": "Ireland & UK", "amount": 500},
      {"date": "2026-07-24", "number": "2026-1036", "customer": "Studio Halfweg", "region": "Benelux", "amount": 300},
      {"date": "2026-07-28", "number": "2026-1037", "customer": "Fjeldstad Papir", "region": "Nordics", "amount": 950},
      {"date": "2026-07-30", "number": "2026-1038", "customer": "Boekhandel Van Rossem", "region": "Benelux", "amount": 1000},
      {"date": "2026-08-04", "number": "2026-1039", "customer": "Folio & Sons", "region": "Ireland & UK", "amount": 150},
      {"date": "2026-08-06", "number": "2026-1040", "customer": "Lindqvist Förlag", "region": "Nordics", "amount": 1280},
      {"date": "2026-08-11", "number": "2026-1041", "customer": "Galerie Noord", "region": "Benelux", "amount": 480},
      {"date": "2026-08-13", "number": "2026-1042", "customer": "Folio & Sons", "region": "Ireland & UK", "amount": 3683},
      {"date": "2026-08-14", "number": "2026-1043", "customer": "Hatch & Lane, Bristol", "region": "Ireland & UK", "amount": 48},
      {"date": "2026-08-18", "number": "2026-1044", "customer": "Museum of Print, Antwerp", "region": "Benelux", "amount": 450},
      {"date": "2026-08-19", "number": "2026-1045", "customer": "Studio Halfweg", "region": "Benelux", "amount": 800},
      {"date": "2026-08-21", "number": "2026-1046", "customer": "Fjeldstad Papir", "region": "Nordics", "amount": 330},
      {"date": "2026-08-25", "number": "2026-1047", "customer": "Galerie Noord", "region": "Benelux", "amount": 2150},
      {"date": "2026-08-26", "number": "2026-1048", "customer": "Folio & Sons", "region": "Ireland & UK", "amount": 800},
      {"date": "2026-08-27", "number": "2026-1049", "customer": "Boekhandel Van Rossem", "region": "Benelux", "amount": 330},
      {"date": "2026-09-01", "number": "2026-1050", "customer": "Lindqvist Förlag", "region": "Nordics", "amount": 2400},
      {"date": "2026-09-03", "number": "2026-1051", "customer": "Hatch & Lane, Bristol", "region": "Ireland & UK", "amount": 1125},
      {"date": "2026-09-08", "number": "2026-1052", "customer": "Studio Halfweg", "region": "Benelux", "amount": 620},
      {"date": "2026-09-09", "number": "2026-1053", "customer": "Museum of Print, Antwerp", "region": "Benelux", "amount": 1880},
      {"date": "2026-09-11", "number": "2026-1054", "customer": "Fjeldstad Papir", "region": "Nordics", "amount": 175},
      {"date": "2026-09-15", "number": "2026-1055", "customer": "Folio & Sons", "region": "Ireland & UK", "amount": 2160},
      {"date": "2026-09-17", "number": "2026-1056", "customer": "Galerie Noord", "region": "Benelux", "amount": 725},
      {"date": "2026-09-22", "number": "2026-1057", "customer": "Boekhandel Van Rossem", "region": "Benelux", "amount": 410},
      {"date": "2026-09-23", "number": "2026-1058", "customer": "Lindqvist Förlag", "region": "Nordics", "amount": 640},
      {"date": "2026-09-25", "number": "2026-1059", "customer": "Hatch & Lane, Bristol", "region": "Ireland & UK", "amount": 275},
      {"date": "2026-09-29", "number": "2026-1060", "customer": "Studio Halfweg", "region": "Benelux", "amount": 1440}
    ]
  }
}
```

## Use it

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

```ts title="summary-report.ts"
import { writeFileSync } from 'node:fs'
import { quario } from 'quario'
import { pdf } from '@quario/pdf'
import summary from './summary-report.report.json' with { type: 'json' }

const q = quario({ locale: 'en-IE', currency: 'EUR' })   // what `format` presents through
const report = q.report(summary)

const data = await getSales('2026-Q3')                // your ledger
const bytes = await report.render(pdf({ page: { size: 'A4', margin: 54 } }), data)

writeFileSync('sales-summary.pdf', bytes)
```

The same compiled report renders to a workbook with nothing to change — see
[JSON to Excel](https://getquario.com/guides/json-to-excel.md). Every section lands with its subtotal row, and every amount
is a number.

## Make it yours

Because the report is declarative, changing it means changing data. A second level — region, then
customer — is a second entry in `groups` with its own aggregates and its own header. A top-ten is
a `sort` on the amount and `"take": 10` on the report or on a group. A page per region is
`"break": "between"` on the group. That keeps the first region with the KPI tiles and starts each
one after it on a fresh page. `"before"` instead gives the tiles a cover page of their own. Swap
the KPI row for the ones your reader asks about, and the
sections follow whatever field you group on. All of it diffs in a pull request, and the same file
renders identically in Node, the browser, and at the edge. It renders **without `eval`**, so
customer names from the ledger never become code. [Security →](https://getquario.com/docs/diving-deeper/security.md)

## FAQ

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

Yes — `@quario/pdf` renders the same definition straight to PDF bytes, with no browser anywhere,
and it’s the target that draws the page bands. [Full example →](https://getquario.com/guides/json-to-pdf.md)

### Can I export the report to Excel?

Yes — `@quario/xlsx` renders the same definition to a workbook, with nothing to change. Each
section is its own block of rows with a bold header. The subtotal rows carry `"format":
"currency"` so they’re real numbers, and the Date column is a typed date that sorts and
filters as one. [Full example →](https://getquario.com/guides/json-to-excel.md)

### Where do the sections come from?

From the data. A group opens one instance per distinct key it meets. A fourth region in next
quarter’s ledger is a fourth section with no change to the definition. Sort the rows by the group
field, or the same key opens more than once.

### Can I chart it?

Yes, by drawing the chart in your code. quario lays out text, tables and images, so a chart reaches
the page as an `image` item. Hand it SVG bytes from a library such as d3 or ECharts, and the PDF
draws them as vectors. The workbook and Word targets refuse an SVG, so a render to those sends PNG
bytes instead. The [revenue report](https://getquario.com/templates/revenue-report.md) is a template built that way.
[Charts in a PDF →](https://getquario.com/guides/charts-in-pdf.md)

### Where are the other templates?

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