Create your own types
Create your own types to give related values a common shape, keep them consistent, and catch mistakes early
A custom type lets you describe a group of related settings once, give each setting a name and a type, and reuse that group across your project.
This guide walks through four ways custom types help with reuse. All of them use the same example: the display settings of a metric (its label, number format, and description).
Give shared settings a shape
Every measure and metric has a label, a format, and often a description. Without a shared definition, each analyst writes them a little differently. A custom type describes the shape once:
Type MetricDisplay {
label: String
format (default: '#,###'): String
description (optional): String
}
MetricDisplay revenue_display {
label: 'Revenue'
format: '[$$]#,###'
description: 'Total order value after discounts'
}format falls back to '#,###' when it's left out, and description can be skipped. Everything else is required, and AML checks each value against the type. A typo is caught right in the editor:
MetricDisplay revenue_display {
label: 'Revenue'
fromat: '[$$]#,###' // error: Property 'fromat' does not exist on type 'MetricDisplay'.
}Keep values consistent
Free-form strings drift: one analyst writes '[$$]#,###', another '$#,###.00'. To keep everyone on the same few options, list the allowed values in a type and use it for the field:
Type MetricFormat = '#,###' | '[$$]#,###' | '#,###.0%'
Type MetricDisplay {
label: String
format (default: '#,###'): MetricFormat
description (optional): String
}
MetricDisplay refund_rate_display {
label: 'Refund rate'
format: '0.00 %' // error: Type 'String' is not assignable to type 'MetricFormat'
}Adding a new format now means adding it to MetricFormat, which is a single change everyone can review.
Pass one value to a function, not many parameters
When you build measures with a function, take the whole MetricDisplay as one parameter instead of separate label, format, and description parameters:
Func sum_measure(column: String, display: MetricDisplay) => Measure {
Measure {
label: display.label
format: display.format
description: display.description
type: 'number'
definition: @sql {{ #SOURCE.${column} }};;
aggregation_type: 'sum'
}
}
Model orders {
// ...
measure revenue: sum_measure('amount', revenue_display)
measure units_sold: sum_measure('quantity', MetricDisplay { label: 'Units sold' })
}Each call stays short and readable, and adding a new display setting later only changes the type and the function, not every call.
Build bigger settings from smaller ones
Custom types can contain other custom types. For example, you can keep the display settings of a whole area in one catalog, so they're easy to find and review together:
Type MetricCatalog {
revenue: MetricDisplay
orders: MetricDisplay
average_order_value: MetricDisplay
}
MetricCatalog sales_metrics {
revenue: MetricDisplay { label: 'Revenue', format: '[$$]#,###' }
orders: MetricDisplay { label: 'Orders' }
average_order_value: MetricDisplay {
label: 'Average order value'
format: '[$$]#,###'
description: 'Revenue divided by the number of orders'
}
}
sales_metrics.average_order_value.label // 'Average order value'Result
- Shared settings have one agreed shape, and AML checks every value against it.
- Allowed values are listed in one place, so everyone stays consistent.
- Functions take one well-named value instead of a long list of parameters.
- Bigger types are built from smaller ones, so building blocks are reused.
To learn more about declaring types, see AML Types.
Create new models from existing dimensions and measures
This document describes how to use AML Extend to create a new data model from existing models
AQL Overview
AQL is a purpose-built query language where metrics are first-class composable objects. It is what makes a semantic layer expressive, and the reason both AI and humans can reason from real business definitions instead of raw schema.