Column locators

The full `from` vocabulary — column, attribute, relation, reference and computed locators, the exact form each one takes, and the types a column can be cast to.

Every column declares where its value comes from with a from locator. There are five verbs, and the form of each is checked against a pattern when the file is validated — so a typo is a deploy-time error, not a null column.

The vocabulary

LocatorResolves to
column:<col>A scalar column on the source entity.
attribute:common.<key>One key from the entity's common attributes.
attribute:common.*All common keys, as a single JSON bucket.
attribute:locale.<key>One locale-scoped attribute, resolved at default_locale.
relation:<fk>→<entity>.<col>Follows a foreign key to one row and takes a column off it.
relation:<join>→<entity>.<col>[]Aggregates a column across a join table into an array.
relation:<join>→<entity>.<col>.<lk>[]The same, for a localised label — resolved at default_locale.
reference:<attr>.labelA reference record's label, resolved from the code held in that attribute.
computed:<name>A platform-provided computed value.
The arrow in a relation locator is a literal (U+2192), not ->. It is the single most common validation failure in this file. Copy it from an example rather than typing it.

Locator names are lowercase identifiers, matching the entity, column and attribute names in your schema.json.

column: — the plain case

analytics.json
{ "name": "code", "from": "column:code", "type": "text", "optional": false }

A scalar column on the source entity, taken as it is. The column keeps its source type unless you cast it with type. This is most of a typical view.

attribute: — lifting values out of a bucket

Your entity's attributes are not scalar columns; they live in structured buckets, split between values that apply everywhere and values that are per locale.

analytics.json
{ "name": "material", "from": "attribute:common.material", "type": "text" },
{ "name": "label",    "from": "attribute:locale.display_name", "type": "text" },
{ "name": "attrs",    "from": "attribute:common.*", "type": "jsonb" }
  • attribute:common.<key> lifts one key into a column.
  • attribute:locale.<key> lifts one locale-scoped key, resolved at the view's default_locale. A view is one row per key, not one row per locale — for a per-locale breakout, declare explicit extra columns.
  • attribute:common.* takes the whole bucket as a JSON column. Useful as an escape hatch; not useful as a dimension, because nothing can group by an opaque bucket. Prefer named columns for anything an operator will chart.

relation: — following and aggregating

Two shapes, and the difference is cardinality:

Scalar — follow a foreign key to exactly one row and take one column:

analytics.json
{ "name": "zone", "from": "relation:zone_id→zones.code", "type": "text" }

This is how a foreign key becomes something readable. An operator can group by zone; nobody wants to group by a UUID.

Array — aggregate a column across a join into an array, so the row does not fan out:

analytics.json
{ "name": "tags", "from": "relation:product_tags→tags.code[]", "type": "text[]" }

The trailing [] is what makes it an aggregate. Without it you are asserting one row on the far side, and a to-many relation would multiply your rows — which a flat view will not do.

The third form adds a localised label to the array case: relation:<join>→<entity>.<col>.<lk>[] resolves the label at default_locale before aggregating, so you get an array of readable names rather than an array of JSON.

reference: — resolving a code to its label

analytics.json
{ "name": "unit", "from": "reference:unit_code.label", "type": "text" }

Where an attribute holds a code from a reference list, this resolves that code to the list record's label. Same motivation as a scalar relation: store the code, report the label.

computed: — platform-provided values

analytics.json
{ "name": "completeness", "from": "computed:completeness", "type": "numeric" }

A value the platform computes rather than one you store. Which computed values exist is a platform matter, not something your app declares — so treat this as the narrow case it is, and validate against the published schema if you are reaching for one.

Types

type is the type the column is cast to. Inferred when omitted — text for strings and attributes, the source column's type for column:, a text array for array relations. The allowed values:

texttext[]intbigint
numericbooltimestamptzdate
uuidjsonb

Cast deliberately for anything with a semantic role. A time role wants timestamptz or date; a measure wants numeric, int or bigint. A number that arrives as text cannot be summed, and the studio's fallback heuristic will not treat it as a measure — see Semantic roles.

optional

Defaults to true: the column may be null, and no error is raised if the source attribute or relation is absent. Set "optional": false on columns that can never be null — your key, your timestamp, anything a chart depends on existing.

Where to go next

Was this page helpful?