---
title: "ReportSchema — API reference"
description: "Every field of a quario report definition and its nested types: groups, table detail, items, splits, images and page bands."
url: "https://getquario.com/docs/reference/report-schema"
---

# ReportSchema

The document shape. Every object with fixed keys is a closed set, so an unknown
key anywhere is a definition error. The same shape ships as a JSON Schema,
`quario/schema.json`, which [Validating
definitions](https://getquario.com/docs/diving-deeper/validating.md#checking-against-the-json-schema)
sets beside `plan`.

## The document

| Field | Type | Meaning |
| --- | --- | --- |
| `$schema` | `string` | Where an editor finds the document’s JSON Schema. The engine checks the type and reads nothing else. Top level only. |
| `data` | `string` | Required. The JSONPath selecting the rows the report walks. |
| `params` | `Record<string, JsonValue \| ParamDeclaration>` | The report’s parameters, read as `$.params`. A bare value is the parameter’s value, and a plain object declares one. A render may supply either. |
| `where` | `` `=${string}` `` | Drops rows that fail it, before aggregates. |
| `sort` | `SortKey[]` | Orders the whole set, applied left to right. |
| `take` | `number` | Keeps this many rows after the sort. Integer >= 1. Dropped rows leave aggregates and runners too. |
| `aggregates` | `Record<string, ReducerSpec>` | Report-level aggregates, injected bare under `$`. |
| `run` | `Record<string, ReducerSpec>` | Running accumulators, read as `run.<name>` on detail rows. |
| `style` | `ReportStyleDeclarations` | The report default. `family` and `size` only. |
| `header` | `Item[]` or `{ height, items }` | The report header band. The object form pins a box from the page top. |
| `empty` | `Item[]` | Replaces the whole body when no rows remain. |
| `columns` | `number` | Flows the body in this many page columns. Integer >= 2, and no nesting. |
| `groups` | `Group[]` | Group bands, outermost first. |
| `detail` | `Item[]` or `TableDetail` | The per-row band. |
| `footer` | `Item[]` | The report footer band. |
| `page` | `PageBands` | Furniture paginated targets draw on every page. |
| `required` | `Partial<Record<RequirableName, true>>` | Declarations this document can’t do without, checked by `plan` against the targets you name. |

### `ParamDeclaration`

```ts title="signature"
interface ParamDeclaration {
  default?: JsonValue
  type?: ParamType
  label?: string
}

type ParamType = 'string' | 'number' | 'boolean' | 'array' | 'object'
```

A declaration without a `default` is a required parameter, and the engine
refuses a render that supplies none before any expression runs. A `null` default beside a
`type` makes it optional. `label` is the text a person reads for the
parameter, and the engine never renders it. A value that’s itself an object
goes under `default`, because any plain object here is a declaration.

## `Group`

```ts title="signature"
interface Group {
  name: string
  by: `=${string}`
  break?: 'before' | 'between' | 'after' | 'around'
  reset?: 'page'
  label?: string
  collapsed?: boolean
  columns?: number
  sort?: SortKey[]
  take?: number
  instances?: { sort?: SortKey[]; take?: number }
  aggregates?: Record<string, ReducerSpec>
  run?: Record<string, ReducerSpec>
  header?: Item[]
  footer?: Item[]
}
```

`name` is the handle its bands and everything nested inside them read. It must
be a plain identifier, unique across the document, and can’t be `@`, `$`,
`run`, `loop` or `page`. A group aggregate can’t take the name `key`. It
folds the rows alone, and reading a sibling aggregate through the group’s
handle is a render error.

`break` says where this group’s instances turn a page. Every boundary between
consecutive instances turns. `"before"` adds the run’s leading edge, `"after"`
its trailing edge, `"around"` both, and `"between"` neither. `reset: "page"`
restarts `page.number` / `page.total` at each instance and turns no page itself,
so it requires `break` to be `"before"` or `"around"`.

`instances` orders and cuts the group’s instances, as its `sort` and `take`
order and cut the rows inside each one. An `instances.sort` key reads the
group’s own handle, the handles around it and `$`. `@` and `run` are unbound
there. A dropped instance never renders, and the aggregates around it still
count it. Without `instances`, instances render in first-seen order. [Selecting
rows](https://getquario.com/docs/diving-deeper/selecting-rows.md#ranking-the-instances) has the order
of operations.

`label` names an instance for navigation rather than for the page. It’s a
template resolved once per instance in group scope. There the instance’s own
handle and `$` bind, and `@` stays unbound as in any group band. A target with
somewhere to put a name spends it there: a PDF bookmark, the viewer’s outline
row, a worksheet tab. The others withdraw it, so no page changes. A value
that isn’t a template string is a definition error, and a hollow instance stays
unlisted whether it declares one or not.

`collapsed` asks that the instance open shut. It renders its header and footer
and leaves out the content between them. Nothing touches the aggregates, so the
rows stay in the partition and a footer still totals them. It’s one literal,
`true` or `false`, and an expression is a definition error.

## `TableDetail`

```ts title="signature"
interface TableDetail {
  header?: TableHeaderBox
  row?: TableRow
  columns: [TableColumn, ...TableColumn[]]
  total?: TotalBlock
}
```

### `TableColumn`

```ts title="signature"
interface TableColumn extends Omit<Cell, 'style'> {
  header?: string | TableHeader
  width?: number
  style?: CellStyleDeclarations
}
```

`width` is a literal percentage from 0 to 100, and it’s the border-box share.

### `TableHeader`, `TableRow`, `TableHeaderBox`

```ts title="signature"
interface TableHeader { value: CellValue; style?: StyleDeclarations; span?: number }
interface TableRow { visible?: ExpressionValue<boolean>; style?: RowStyleDeclarations }
interface TableHeaderBox { style?: RowStyleDeclarations }
```

A row’s `style` resolves onto that row’s cells on every target.

### `TotalBlock`, `TotalRow`, `TotalCell`

```ts title="signature"
interface TotalBlock extends TableRow { rows: [TotalRow, ...TotalRow[]] }
interface TotalRow {
  cells: [TotalCell, ...TotalCell[]]
  visible?: ExpressionValue<boolean>
  style?: RowStyleDeclarations
}
type TotalCell = Omit<Cell, 'style'> & { span?: number; style?: CellStyleDeclarations }
```

What every total row shares goes on the block. Each row must cover the columns
exactly once, counting `span`.

## Items

### `Cell` and `TextItem`

```ts title="signature"
interface Cell {
  value: CellValue
  visible?: ExpressionValue<boolean>
  style?: StyleDeclarations
}
interface TextItem extends Omit<Cell, 'style'> {
  type: 'text'
  style?: StackedStyleDeclarations
}
type CellValue = string | StyledRun[]
interface StyledRun {
  value: string
  style?: RunStyleDeclarations
}
```

A cell `value` is one template string or a non-empty array of styled runs. A
cell written as one string is one run, so a one-run array says exactly what that
string says. A run’s `style` is literal only, never an expression, and a run has
no `visible`, since `{{#if}}` already conditions its text. A CR, LF, or CRLF in the evaluated text
is a hard line break.

### `ImageItem`

```ts title="signature"
interface ImageItem {
  type: 'image'
  source: `=${string}`
  fit?: 'natural' | 'width'
  alt?: string
  visible?: ExpressionValue<boolean>
  style?: ImageStyleDeclarations
}
```

`source` must be an expression resolving to PNG, JPEG, or SVG bytes. An SVG
states its size through absolute `width` and `height` on its root, or a
`viewBox`, and the XLSX and DOCX targets refuse one. A nullish source
skips the item, so an optional logo needs no guard. `style` covers
`background`, `align`, and the box and flow spacing, and nothing else. `alt` is
one template string, the one cell value the run form doesn’t reach.

### `SplitItem`

```ts title="signature"
interface SplitItem {
  type: 'split'
  slots: [SplitSlot, SplitSlot, ...SplitSlot[]]
  visible?: ExpressionValue<boolean>
  style?: StyleDeclarations
}
```

Two slots at minimum. A slot is a text or image item plus an optional `width`
percentage, and it takes no flow spacing. Splits don’t nest, and neither
appears in a table or total cell. The split’s `style` is the layer under its
slots, and it ranks above the band’s role default.

### `GridItem`

```ts title="signature"
interface GridItem {
  type: 'grid'
  columns: [GridColumn, GridColumn, ...GridColumn[]]
  rows: [GridRow, ...GridRow[]]
  visible?: ExpressionValue<boolean>
}

interface GridColumn {
  width?: number
  style?: SlotStyleDeclarations
}

interface GridRow {
  slots?: GridSlot[]
  visible?: ExpressionValue<boolean>
  style?: StyleDeclarations
}

type GridSlot = Omit<SplitSlot, 'width'>
```

Two columns and one row at minimum. A row is a split without `type`, and it
reaches the event stream as one. A column’s `width` follows a slot’s rules,
and its `style` is the layer under each slot in that position. A slot takes no
`width` of its own. A row naming fewer slots than there are columns leaves the
rest empty, so `{}` is a spacer line.

### `Item`

```ts title="signature"
type Item = TextItem | ImageItem | SplitItem | GridItem
```

## `PageBands`

```ts title="signature"
interface PageBands {
  header?: Item[]
  footer?: Item[]
  margin?: number | PageMargin
  size?: PaperName
  orientation?: Orientation
}

interface PageMargin { top: number; right: number; bottom: number; left: number }
type PaperName = 'A4' | 'letter'
type Orientation = 'portrait' | 'landscape'
```

`margin`, `size` and `orientation` are the paper. Each key may come from the
document or from the target’s options, and declaring the same key in both is a
render error. `orientation` turns whichever paper is in force. A `[width,
height]` size is a target option only.

## `SortKey`

```ts title="signature"
interface SortKey {
  by: `=${string}`
  dir?: 'asc' | 'desc'
}
```

`dir` defaults to `"asc"`. The sort is stable. Keys compare with JavaScript
ordering, so keep a key one type where the order matters.

## Supporting types

```ts title="signature"
type ExpressionValue<T = unknown> = T | `=${string}`
type ReducerSpec = string
type JsonValue =
  | null | boolean | number | string
  | JsonValue[]
  | { [key: string]: JsonValue }
```

A leading `=` on any property other than a cell `value` makes it an expression.
A parameter is a JSON value and nothing else, whether it’s a bare value, a
`default` or a value a render supplies. A `Date`, a `Map`, a `NaN` or an
`Infinity` is an error.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://getquario.com/"},{"@type":"ListItem","position":2,"name":"Docs","item":"https://getquario.com/docs"},{"@type":"ListItem","position":3,"name":"ReportSchema","item":"https://getquario.com/docs/reference/report-schema"}]}
```
