Building reports
Parameters, variables & queries
The three separate data concepts in a Golden Reports report and the rules that bind them - including the man-in-the-middle rule.
A report has three data concepts: parameters, query arguments, and variables. They are not the same thing, and the editor enforces their separation. This is the single most important rule to learn before you build anything non-trivial.
Every report has exactly one GraphQL query that runs against its data context (which is itself a GraphQL schema). That query is the report's single fetch; everything the template renders is either a result of that query or a variable.
A short glossary
- Parameters are the report's external contract - its public API. They're how the outside world feeds values into the report. A user can supply them via a popup before running the report, or a calling system can pass them programmatically in an embed URL or an export request.
- Query arguments are the operation variables of the report's GraphQL query (e.g.
query($id: ID!) { customer(id: $id) { ... } }). Each argument is either a literal or a reference to a parameter. - Variables are named values available inside the Liquid template. The template (
{{ order.total | money }}) can reference the query result and variables - that's it.
| Concept | Defined where | Read by |
|---|---|---|
| Parameter | Data panel → Parameters tab | Nothing directly. Mapped into the query or into variables. |
| Query argument | Data panel → Query tab | The GraphQL query the platform runs against the data context. |
| Variable | Data panel → Variables tab | Liquid expressions inside the template. |
The man-in-the-middle rule
A parameter cannot appear directly in a Liquid template. To make $currency show up in a footer:
- Define a parameter
currency(typeString, defaultUSD). - Define a variable
currency = $currencyin the Variables panel. - Reference
{{ currency }}in the footer'stextblock.
Why the rule exists: it keeps the binding graph traceable. If parameters could appear anywhere, refactoring a parameter would mean grepping the entire template; with the rule, you know that every consumer of a parameter is registered as either a query argument or a variable. The editor can also typecheck that path - a parameter typed as Date mapped to a variable expecting a String is caught at design time.
If you only remember one thing
Parameters feed query arguments and variables. The template reads the query result and variables. The template never reads parameters directly.
A worked example
Let's say you're building an invoice report. The caller will pass the invoice ID and the customer's preferred currency.
Parameters
| Name | Type | Default |
|---|---|---|
invoiceId | ID | - |
currency | String | USD |
Query
One GraphQL query against the Sales data context, fetching the invoice and its lines, with a single argument: $id:
query InvoiceData($id: ID!) {
invoice(id: $id) {
number
date
total
lines {
description
quantity
unitPrice
subtotal
}
}
}
In the Query tab, you bind that $id argument to the parameter $invoiceId. When the report runs, the parameter's runtime value is substituted in before the query executes.
Variables
| Name | Value | Notes |
|---|---|---|
currency | $currency | Just exposes the parameter to templates. |
today | Literal now | Today's date, computed at render time. |
vipLine | {% if invoice.total > 10000 %}VIP customer{% endif %} | Computed string. |
Templates
In the table column for "Subtotal" you write:
{{ line.subtotal | money: currency }}
line.subtotal is from the query result; currency is a variable that originated as a parameter. Neither $currency nor $invoiceId appear directly in the template.
Why the separation pays off
- Embedding. A consumer embedding the report only needs to know the parameter names -
invoiceIdandcurrency. They never see internal query argument names or variable identifiers, so you can rename things freely. - Typing. Each parameter has a declared type. Bindings to query arguments and variables are typechecked at design time; type-incompatible bindings are highlighted with a red chip in the editor.
- Reusability. Variables that compute values (like
vipLineabove) can be referenced from many blocks. Variables are the right place for "computed once, used many times" logic - not inline Liquid spread across 30 text blocks. - Programmatic access. When the report is run through the API, the request only specifies parameter values. The platform handles propagating them through the query and variables. This is what makes embedding stable.
Parameter types
Parameters are typed. The supported primitives are:
| Type | Example |
|---|---|
String | "USD" |
Int | 42 |
Float | 3.14 |
Boolean | true |
Date | 2026-05-13 |
DateTime | 2026-05-13T10:30:00Z |
ID | "inv_01HABCXYZ" |
A parameter can be optional (with a default) or required. Required parameters without a runtime value cause the report to fail validation before any query runs.
Complex inputs (lists, structured objects) are typically modelled as multiple parameters; the platform doesn't currently support nested object parameters at the report boundary.
How values flow at render time
The data layer sees only query arguments. The template sees only query results and variables. The parameter layer is the boundary the outside world interacts with.
Next
- See how Liquid expressions are evaluated: Liquid templating.
- Learn what data contexts your query can target: Data contexts.
- Learn how a parameterized report becomes an embeddable URL: Preview & export.