---
title: "Scopes — quario"
description: "What a quario expression can read, and where: the row anchor, the report root, group handles, running totals and the page."
url: "https://getquario.com/docs/diving-deeper/scopes"
---

# Scopes

## Intent

quario assembles a scope per band, and it decides which names an expression
may read. Most authoring mistakes are anchor mistakes. A group header reads
`@`. A detail cell reads a handle that’s not in scope. An
expression reads a page number where no target has decided page numbers yet.
Knowing which names bind where turns those from render errors into lines you
never write.

## Design decisions

**`@` and `$` are the two ends of one chain.** Reports nest as deep as you
like, but only two anchors stay fixed. `@` is the row in front of you and `$`
is the report root. You reach everything between them by name, which keeps an
expression’s meaning independent of how many groups happen to enclose it.

**A named group handle covers every level in between.** A group’s `name`
becomes the handle its own bands read, along with everything nested inside
them. A detail row three groups deep reads `@.price`, `region.subtotal` and
`$.grandTotal` in one expression, because scopes inherit down the chain.
Renaming a group is the one edit that moves an expression’s meaning.

**Group bands leave `@` unbound.** A report header, a group footer and an
`empty` band have no single current row, so quario binds nothing rather than
picking one. `@.anything` throws there. Read the group’s own values through its
handle. Where a band needs the whole row it grouped by, declare `by: "=@"` so
the handle’s key is that row.

**`$.input` carries the whole render payload.** `data` narrows the report to a
set of rows, and a header usually needs the document those rows came from.
`$.input` is the object you passed to `render`, untouched. The renderer copies
nothing onto `$`, so an invoice number stays where your application put it.

**`$.params` holds the report’s parameters.** Each takes the value a render
supplies, or else the one the document wrote, as a bare value or a `default`.
Either way it’s a JSON value, which is what separates it from
`$.input`. A `Date`, a `Map`, a `NaN` or an `Infinity` is an error. [Supplying
the data](https://getquario.com/docs/diving-deeper/supplying-data.md) covers declaring one.

**Every absent read resolves to `null`.** A missing variable, a missing field
and the values quario injects all come back `null`, so `x == null` holds
without special handling. Reading a property through a null base still throws,
which is what `?.` is for.

**Anchors pass through the same prototype guard as any property.** `@` and `$`
are ordinary identifiers to the expression engine. `$.constructor` throws
as `@.constructor` does. An anchor isn’t a way around the guard.

**An asynchronous function reads the row and nothing else.** A synchronous
function keeps every scope this page describes. The engine settles an `async`
one against the row alone, before the walk starts. Its arguments read `@`,
`$.params`, `$.input` or a literal, and a group handle, an aggregate, and a
running value are out of reach. That rules it out of every position that steers
the walk. [Host functions](https://getquario.com/docs/diving-deeper/host-functions.md) lists them, and
carries the rest.

**`page` binds inside page bands and nowhere else.** Page numbers exist only
after a target decides where the pages fall, which happens after it walks the
body. Binding `page` in a detail cell would promise a number that no
unpaginated target could supply.

## API walkthrough

### Reading the current row

**`@`** is the detail row the walk sits on. quario binds it in detail items
and in the per-row expressions that decide the walk, which are `by`, `where`,
`sort`, and the aggregate and running expressions. Outside those it’s unbound, and
reading through it throws rather than resolving to null. That fail-loud
behaviour is deliberate, because a silently null row is a report that renders
blank cells and ships.

**`@.field`** reads one field of that row. Arithmetic on it happens in the
expression, so `@.qty * @.unitPrice` keeps the calculation in the definition
your reviewers can read.

**`by: "=@"`** makes a group key the whole row rather than one of its fields.
That’s the route for a group band that needs everything about the row it
opened on, since `@` isn’t bound in the band itself.

### Reading the report root

**`$`** is the synthetic report root, and it’s in scope in every band. It’s
not your input document. It’s the root quario assembles, with your document
nested inside it.

**`$.input`** is that whole document, unflattened. A report whose `data` is
`$.invoice.lines[*]` still reads `$.input.invoice.number` in its header, which
is how one compiled report serves both the rows and the metadata around them.

**`$.params`** holds the report’s parameters, each the value a render supplied
or the one the document wrote. Use it for values that belong to the document
rather than to the data, such as a threshold a conditional style compares
against.

**`$.<aggregate>`** reads a report-level aggregate by the name you declared it
under. Aggregate names go bare under `$`, which is why `input` and `params`
stay reserved and a group aggregate can’t take the name `key`.

```json title="three anchors in one expression"
{
  "value": "{{ @.amount }} of {{ region.subtotal }}, invoice {{ $.input.invoice.number }}"
}
```

### Reading an enclosing group

**`<groupName>.key`** is the value that group instance grouped by. For a group
named `project` grouping on `=@.project`, `project.key` is the project name.

**`<groupName>.<aggregate>`** is one of that instance’s aggregates, scoped to
the rows in it. `project.lineTotal` covers one project while `$.subtotal`
covers the document, and the two are readable side by side because the handle
and the root are both in scope.

Handles are unique across the document and can’t take the engine’s own names,
so the engine rejects `@`, `$`, `run`, `loop` and `page` as group names.

### Reading a running total

**`run.<name>`** is a running accumulator on a detail row, holding a different
value per row according to the rows before it in render order. Declare it under
`run`, and the name is yours. The reducers are the six aggregate ones plus
`prev`, which holds the previous row’s value and is `null` on the first.

Scope decides when a runner resets. A `run` block on the report persists across
the whole document. A `run` block on a group starts again at each instance of
that group, which is what a per-invoice running balance wants.

When no `run` block is in scope at all, `run` itself is unbound and
`run.<name>` throws, the same fail-loud rule `@` follows. With any block in
scope, an undeclared name reads as `null` instead.

```json title="printing a repeated value once per change"
{
  "run": { "prevRegion": "prev:=@.region" },
  "detail": {
    "columns": [
      { "header": { "value": "Region" }, "value": "{{ @.region }}",
        "visible": "=@.region != run.prevRegion" }
    ]
  }
}
```

A hidden table cell keeps its slot, so suppressing the repeats leaves the
column aligned. Filtering with `where` isn’t the same move, because it would
drop those rows from the aggregates and the runners too.

### Reading the page

**`page.number`** is the current page and **`page.total`** is the count for the
current sequence, both 1-based. A group declaring `reset: "page"` beside a
`break` of `"before"` or `"around"` starts a new sequence. That’s how a
three-page invoice reads as `1 of 3` inside a batch of two hundred.

Both bind in page band items only. Targets that don’t paginate ignore the band
rather than guessing a number.

### Handling an absent value

**`??`** supplies a default for a read that came back null, as in
`@.discount ?? 0`.

**`?.`** reads through a base that may itself be null. `@.address?.city` is
safe where `@.address.city` throws, because absence normalizes to null one
level at a time.

**`== null`** is the test that holds for every kind of absence under the
expression engine’s strict equality. One comparison covers a missing field, a
missing variable, and an injected null alike.

```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":"Scopes","item":"https://getquario.com/docs/diving-deeper/scopes"}]}
```
