Declaring a view

Every field of a view — name, source, key, columns, default_locale, depends_on, title and description — what flattening a column or relation means, and the dataset name the platform emits.

A view is one flat, analysable table over one entity your App owns. You declare the columns and where each comes from; the platform builds the view.

analytics.json
{
  "name": "locations",
  "source": "locations",
  "key": "id",
  "title": "Stock locations",
  "description": "One row per location, with its type and market.",
  "depends_on": ["zones"],
  "roles": {
    "time": "created_at",
    "entity": "location_id",
    "measures": ["capacity"],
    "dimensions": ["type", "zone"]
  },
  "columns": [
    { "name": "location_id", "from": "column:id", "type": "uuid", "optional": false },
    { "name": "code", "from": "column:code", "type": "text", "optional": false },
    { "name": "capacity", "from": "column:capacity", "type": "numeric" },
    { "name": "zone", "from": "relation:zone_id→zones.code", "type": "text" }
  ]
}

The view fields

FieldRequiredWhat it does
nameLogical view name, lowercase with underscores. Decides the emitted dataset name (below).
sourceThe entity from your schema.json this view is built from.
columnsThe flat columns of the view. At least one.
keyPrimary key column on the source entity. Defaults to id.
default_localeLocale used to collapse attribute:locale.* columns into a single scalar. Defaults to en. Analytics is one row per key, not one row per locale — add explicit columns if you need per-locale breakouts.
depends_onOther source entities this view reads from through relations. Documents the dependency.
title, descriptionHow the dataset is labelled in the catalog. Without a title the name is humanised.
rolesSemantic roles. See Semantic roles.

name, source and key are lowercase identifiers: a letter first, then letters, digits and underscores. default_locale is a language code, optionally with a region — en, de, en-GB.

Nothing else is allowed inside a view. A stray field is a validation error, not an ignored hint, which is why pointing your editor at the published schema is worth the one line.

Columns

Each column has a name and a from locator:

FieldRequiredWhat it does
nameThe column name in the flat view. Lowercase identifier.
fromWhere the value comes from. See Column locators.
typeThe type the column is cast to. Inferred when you leave it out.
optionalWhether the column may be null. Defaults to true.

Set "optional": false on the columns that can never be null. It is the one piece of information the studio cannot guess, and it is what stops a chart quietly dropping rows.

Declaring type is worth it for anything you will aggregate or scope by time. The inference is good — text for strings and attributes, the source column's type for column:, a text array for array relations — but an explicit numeric or timestamptz documents your intent and protects against a schema change underneath you.

What flattening means

Your entity is not flat. It has scalar columns, but also attributes held in structured buckets, relations to other entities, and references to code lists. A view is flat: one row per key, one scalar per column.

So flattening is the work the locators do for you:

  • A column is taken as it is.
  • An attribute is lifted out of its bucket into a column of its own — and a localised one is resolved at default_locale, because a row cannot hold every language at once.
  • A relation to one row is followed, and one column is taken off the far side. relation:zone_id→zones.code gives you the zone's code as a plain text column, so an operator groups by a readable value instead of a foreign key.
  • A relation across a join is aggregated into an array column, so a row keeps its cardinality instead of fanning out.

That last point is the grain rule restated: a view never multiplies rows. If you need one row per line item rather than per order, that is a second view over the line-item entity, not a relation on the first one. See Modelling for why mixing grains produces plausible wrong numbers.

The emitted dataset name

The name follows a fixed pattern from your manifest's vendor and app name plus the view name:

text
analytics.<vendor>__<app>__<view>

So a view named locations in the acme/warehouse App is registered as analytics.acme__warehouse__locations.

Two consequences worth planning for:

  • The view name is part of a public identifier. Renaming a view renames the dataset, and anything an operator built on the old name is looking at a dataset that no longer exists. Name it for what it is, not for how it is currently implemented.
  • Names have to be unique within your app, and they are namespaced by vendor and app, so you cannot collide with the platform's own datasets or another vendor's.

How many views

One view per grain, and per audience question. A warehouse app might reasonably ship three: locations, movements, and stock levels. What does not work is one wide view that tries to be all three — the roles cannot be right for all of them, and neither can the grain.

Where to go next

Was this page helpful?