Querying the engine
This page belongs to Data App developer guide. Visualizations build their own queries. When app-owned presentation needs live values, queries can supply KPI cards, comparisons, hover cards, data dialogs and annotations. Choose native number mounting or app UI for scalar KPIs according to the presentation needed. Charts and analytical data tables follow the chart rendering policy. The request and response contracts are in the SDK reference; read it first.
Look every field up by name
Section titled “Look every field up by name”Field keys are opaque engine identifiers. Look each one up by the name the live metadata reports and use it verbatim; never assemble, parse or hard-code one.
async function loadAvailableFields() { const fieldMetadata = await app.client.services.engine.fields.getFieldMetadata?.({ semanticLayerId: app.dataBinding.semanticLayerId, datasourceType: app.dataBinding.datasourceType, }); return createVisualizationAvailableFields(fieldMetadata ?? {});}availableFields holds one entry per raw view field and per stored item, told apart by kind:
kind |
Entry |
|---|---|
field |
{ kind, fieldKey, name, view, schema, dataType, aggregations } |
dimension |
{ kind, fieldKey, name, item } |
measure or calculation |
{ kind, fieldKey, name, item } — both are stored measures |
item is the layer’s own item, with its key, name and description. Read the descriptions
before choosing measures: they state grain, units and known gaps. Raw view fields carry no item;
their aggregations list each function the connection offers for that field as
{ name, distinct, resultType }, where distinct: true means the function accepts a distinct
count. See Count distinct values.
Use the exact measures selected by the prepared Business Question. A similarly named
Current or Previous measure may declare date filters or parameters and answer a different
question. Do not substitute it for an unfiltered measure because an example app uses it. If the
required measure is missing or its definition disagrees with the brief, stop and resolve that
mismatch before building the view.
Look up an object’s fields while you prepare that object, inside its own try, as
Check operation results shows. If a lookup fails, show that
object’s error state with the missing field and the available names, and let the rest of the page
load. Do not resolve every field before the page starts: a field one chart needs would then take
down every object that does not use it. Do not show an empty chart as if the query returned no data.
A raw field’s name is not unique. When two views have a column with the same name, such as an
orders view and an updated copy of it, availableFields holds a field entry for each. Match raw
fields on view as well as name, using the view the object’s other queries read, and treat more
than one match as an error that lists each match’s fieldKey and view. Taking the first match can
read the wrong field without any error. Stored items are matched on name.
In App Builder, give the starter’s Chart or useChart a view, and field(name) looks raw
fields up in that view; field(name, view) names the view for one raw field. A name that still
matches several fields fails that object, and its console error lists the matching field keys. When
each match is in a different view, the error also gives the field(name, view) call for each one.
If an app’s src/kit.tsx has no view option, the app was created before the starter had it; match
the view yourself.
Query shapes
Section titled “Query shapes”A semantic layer holds items — stored dimensions and stored measures (calculations) — over the fields of its views. The engine addresses the two differently:
| You want | Send |
|---|---|
| A stored dimension | dimensions: [{ itemKey }] |
| A raw view column as a dimension | dimensions: [{ fieldKey, name }] |
| A stored measure | calculations: [{ itemKey }] |
| An aggregate of a raw column | calculations: [{ calculation: '{m1}', measures: [{ fieldKeys: [fieldKey], aggregation: 'SUM' }] }] |
Sending a stored dimension’s key as a fieldKey, or a stored measure under measures, fails the
whole query with a bare HTTP 400 that does not say which part was wrong. Put measures in
calculations consistently, including raw aggregates.
Visualization definitions are different: they take fieldKey for both and the compiler resolves
it. The table above applies only to queries you build yourself.
const topChannels = () => app.client.services.engine.getData({ semanticLayerId: app.dataBinding.semanticLayerId, query: { dimensions: [{ itemKey: channelKey }], calculations: [{ itemKey: revenueKey }], orders: [{ columnIdx: 1, direction: 'desc' }], limit: 10, }, });orders addresses a result column by its zero-based position — dimensions first, then
calculations — not by its key. See Ordering.
To filter a query by the page’s selections, see Re-query your own objects only on applied changes.
Count distinct values
Section titled “Count distinct values”For an exact distinct count of a raw column, send COUNT with distinct: true:
calculations: [ { calculation: '{m1}', measures: [{ fieldKeys: [customerKey], aggregation: 'COUNT', distinct: true }], },],A field’s aggregations offer this as the COUNT entry with distinct: true; there is no
separate exact-distinct function name. APPROX_COUNT_DISTINCT, where the connection lists it,
returns the warehouse’s estimate, which can differ from the exact count. Use it only when an
estimate is acceptable, and say so beside the figure.
In a visualization definition, aggregation: 'COUNT_DISTINCT' on a raw field gives the exact
count and APPROX_COUNT_DISTINCT the estimate. When the layer has a stored distinct-count
measure, prefer it to either.
Parameters
Section titled “Parameters”A Workbook binds a measure’s parameters to Workbook variables. An app has no Workbook variables, so send the values with the query, on the calculation that declares them:
calculations: [ { itemKey: revenueKey, namedParameters: { formula: {}, filter: { '[Start date].[date]': '2026-01-01', '[End date].[date]': '2026-03-31' }, }, },],- A parameter is keyed by name and type:
[Start date].[date], neverStart date. - Put each value in the slot the measure uses it in —
formulaorfilter. The engine reads a value in the wrong slot as no value at all. - Send only the parameters a measure declares. Different measures declare different sets — a current-period measure may take a start and end date where a period-comparison one takes four. Read what each declares from the metadata rather than sending one fixed list.
Reading the answer
Section titled “Reading the answer”The response contract is in The engine response. In practice:
- Rows arrive as an object keyed by row index; read them with
Object.values(data). - Match columns by the position you asked for them in, not by
fields[i].name, which is a display label. Validate the column count and roles before reading values. - A
datecolumn can arrive as epoch milliseconds rather than an ISO string. Accept both wherever you parse one; ayyyy-mm-ddtest against a number matches nothing, silently. - Totals and the record count live on the response, not in the rows.
- In SDK 0.5.0, a query interrupted because of service capacity is rejected with
AstratoDataCapacityError. Treat this as a failed query and retry later; it does not mean the selected data has no rows. - Attach the query to any error you raise. The engine answers a malformed query with a bare 400, and an error without the query JSON leaves nothing to debug from.
What a Data App can reach
Section titled “What a Data App can reach”A Data App talks to the platform only through the Data App runtime, which grants three
capabilities: semantic-layer:read, datasource-metadata:read and query:execute. Concretely, it
can read the configured semantic layer’s metadata, read the datasource’s aggregation functions and
capabilities, and execute queries against that layer. Any other platform route answers
The Astrato App runtime route does not exist.
Design around that boundary:
- This runtime does not expose Business Question authoring. Establish saved question coverage through the editor or MCP before the App build, as in From exploration to a dashboard. Preserve the complete question meaning in the supported execution/rendering path; dimensions and measures alone omit filters, parameters, ranking and period calculations. See From question to chart for integration limits.
- Query responses carry data, not diagnostics. An app receives the fields, rows, formats,
grouping, metadata, time zone, totals and record count. It never receives the query ID, warehouse
links or execution timings. The SDK does not currently expose the engine SQL
statementto an app; this is not a security boundary, since the response still carries it to the viewer’s browser. See Showing the SQL behind a visualization. - Metadata working does not prove data works. Metadata reads come from Astrato, while queries go to the warehouse. When every query fails but metadata loads, suspect the data connection.
An externally hosted app uses the SDK against your own backend and is bound by what that backend exposes instead.
Writeback, stored-procedure execution and SQL readback are not exposed by the current App CLI or publication transports. Extra OAuth scopes do not add these routes. See App runtime availability; externally hosted apps with an authenticated platform transport can use Writeback and SQL.
Report model defects
Section titled “Report model defects”Report confirmed model defects to the layer owner. Document each app-specific workaround and its reason in the code and README, and verify that it preserves the intended metric meaning.
- Fields the engine refuses. A field whose query fails even on its own cannot be shown. Exclude it with the reason beside it.
- Duplicate rows. Establish the expected grain before deduplicating, and document any deduplication.
- Free text used as a number. Check the full value distribution before writing a parser. Count unparseable values, choose how to handle them and report that choice; do not assume they are rare.
- Missing joins. Report the missing relationship and verify its keys and cardinality before using any app-side matching.
- Dimensions whose values are rendered names. A month dimension that returns
Jan,Febsorts alphabetically. Group a real date into a date part instead; see Charts. - A measure wired to the wrong field. Confirm the intended definition with the layer owner before substituting another calculation.
Check derived colours and icons
Section titled “Check derived colours and icons”Apply each colour, icon or grouping rule across the full dataset and inspect its distribution before using it. A rule that distinguishes a few sample rows may assign most other rows the same result.
When records must remain distinguishable, add a separate cue that stays distinct if derived values collide, such as a record label, outline style or position. Provide a legend for visual encodings.