Inheritanceextend
Declare the delta, not a copy. Edits to the base flow through; the compiler merges and catches conflicts at build time.
Model active_users = users.extend({
dimension activated_at {}
})Semantic layer · Analytics Modeling Language
A semantic layer that’s built like a real programming language, not duct-taped together from YAML and SQL strings. So your business logic stays reusable, composable, and maintainable like real code.
Model orders {
table_name: 'ecommerce.orders'
dimension status { type: 'text' }
dimension created_at { type: 'datetime' }
measure revenue {
type: 'number'
definition: @aql sum(order_items.quantity * products.price) ;;
}
}
# assemble models into a governed dataset
Dataset ecommerce {
models: [orders, order_items, products, users]
relationship(order_items.order_id > orders.id)
relationship(order_items.product_id > products.id)
}Model your metrics, dimensions, and relationships in AML. It compiles to native SQL on your warehouse, and every consumer queries the same governed definitions.
Drag-and-drop exploration and AI chat on governed datasets.
Learn morePull governed metrics into Python or your own product.
Governed metrics in Google Sheets for last-mile analysis.
“We wanted to define analytics logic centrally and have people reuse it, to get faster speed to insight. With Holistics, we can have correct, reusable measurements and metrics going out there.”
Craig WilsonStaff Data Engineer, ZOEYAML was built to serialize data, not to model a business, so when logic repeats you copy-paste it or template SQL with Jinja. AML is a real language: functions, types, and inheritance let you write logic once and reuse it everywhere, so nothing drifts.
Config semantic layer · YAML + Jinja
# 50 country tiles = 50 copies of the same block
tiles:
- type: single_value
title: "United States"
metric: orders.revenue
filter:
field: users.country
operator: is
value: "United States"
# …49 more, copy-pasted, one per country
# reuse? hardcode the list in Jinja and pray:
{% for c in ["United States", "Germany", …] %}
- type: single_value
title: "{{ c }}"
metric: orders.revenue
filter: {field: users.country, value: "{{ c }}"}
{% endfor %}Programmable semantic layer · AML
Func revenue_kpi(country: String) {
VizBlock {
label: country
viz: SingleValue {
dataset: ecommerce
series { field { ref: r(orders.revenue) } }
filter {
field: r(users.country)
operator: 'is'
value: country
}
}
}
}
Dashboard country_kpis {
block kpi1: revenue_kpi('United States')
block kpi2: revenue_kpi('Germany')
// …one function, 50 tiles
}“I have KPI widgets that appear in 50 rows of my dashboard. I took one function, parameterized it, and just pass in the variables. The ability to parameterize a widget and reuse it was a game changer.”
Mike K.Director of Enterprise Analytics, StrongmindextendDeclare the delta, not a copy. Edits to the base flow through; the compiler merges and catches conflicts at build time.
Model active_users = users.extend({
dimension activated_at {}
})FuncTyped, parameterized generators for metrics and partials. Named args, defaults, inferred return types.
Func revenue_for(p: Model) {
metric order_value { … }
}PartialBundle a set of metrics once, then snap it onto any dataset. One metric store, reused everywhere.
Dataset us =
us_market.extend(revenue_pack)Change a threshold in one place; everything downstream updates. Branch logic with if / else.
const HIGH_VALUE = 25000Organize logic into modules and reuse the same object across tenants, so multi-client modeling stays clean instead of copying folders.
A static type system validates as you write, with autocomplete, go-to-definition, and compile-time errors. TypeScript for analytics.
“I can simply extend my existing data model by referencing a different schema. Extend gives me a clean way of doing this, so when the schema changes, I don’t have to worry about editing in two different places.”
Stephen LeeSenior Data Scientist, AuroraEvery definition (models, metrics, dashboards, charts) is plain-text code in Git. Every change goes through a reviewable pull request instead of a silent click in a UI.
metric revenue =
- sum(orders.amount)
+ sum(order_items.quantity * products.price)
+ | where(orders.status != 'refunded')A dedicated analytics query language (AQL) that queries your models, not raw tables, and compiles to native SQL.
revenue
| where(orders.status != 'refunded')
| relative_period(-1 year)Metrics build on other metrics, by name. Redefine the base and everything derived from it updates automatically, with no copies to fall out of sync.
metric revenue =
sum(order_items.quantity * products.price)metric arpu = revenue / count(users.id)metric ltv = arpu * lifespan_monthsRedefine revenue, and arpu and ltv recompute. No copies, no drift.
“The semantic layer is Holistics’ biggest draw, because I was able to build reusable models, dimensions, and metrics, and then describe them as code.”
Sterling ParamoreSr Staff Data Engineer, Mainspring EnergyBecause your whole BI is plain-text AML, you’re never locked into one way of working. Build visually in the GUI, write AML in your IDE, or hand the repo to a coding agent. Every path produces the same governed model.
Model by clicking. The visual builder writes AML for you and stays in two-way sync with the code.
Write AML directly, with types, autocomplete, and Git. Software rigor for your business logic.
Because it’s code, agents develop on it natively: the built-in Copilot, or Claude Code and Cursor in your IDE. They write the AML; you review the PR and validate the SQL.
Agentic developmentNo proprietary engine, no data leaving your warehouse, no logic you can’t read. A programmable layer on top of the stack you already run.
Definitions are plain-text AML in your own Git repo, not rows in a vendor database. Leave anytime, with readable logic.
Every query compiles to native SQL you can read. No black-box execution engine, nothing hidden.
Pushes computation down to your data, so nothing moves and results stay fresh. Snowflake, BigQuery, Postgres, Redshift, Databricks, and more.
Sits above your transformation layer and reads the models you already build. Not a rip-and-replace.
No. YAML in Git is still YAML: no functions, no types, no real composition. Reuse means copy-paste or Jinja string-templating. AML is a typed language where reuse (extend, Func, Partial) is a first-class feature the compiler checks for you.
“We add a new client, copy and paste all the data models, and change the data source. Holistics effectively allows us to be repeatable.”
David AldredInsight Director, Retail InsightGo deeper: AML vs YAML, the reusability guide, and the AQL query language