Metrics — the semantic layer
A metric is one canonical, documented definition of a number — “MRR”, “active users” — as a named, parameterizable SQL template. Query it with dimension and filter overrides so every dashboard reads the same source of truth.
- Read
- 5 min
- Sections
- 4
- Limits
- Free 5 · Pro 50 · Business ∞
Why a semantic layer
Without one, “what is MRR?” has three answers in three dashboards. A metric pins the definition in one place: the SQL, the allowed dimensions, the valid filters, the display format. Everything that consumes it — dashboards, the API, a report — resolves the same way.
Define a metric
The sqlTemplate uses Scriban syntax for safe dimension and filter injection. Declare which dimensions and filters are allowed; anything outside that whitelist is rejected before the query runs.
POST /api/v1/metrics
Authorization: Bearer <token>
Content-Type: application/json
{
"slug": "monthly_active_users",
"displayName": "Monthly Active Users",
"description": "Distinct users with an event in the past 30 days",
"connectionId": "conn-uuid",
"sqlTemplate": "SELECT COUNT(DISTINCT user_id) AS value FROM events WHERE created_at >= NOW() - INTERVAL '30 days' {{ if filters }} AND {{ filters }} {{ end }}",
"dimensionsJson": "[\"country\",\"platform\"]",
"filtersJson": "[{\"name\":\"country\",\"type\":\"string\"}]",
"format": "number",
"tags": ["product","growth"]
}format (number, currency, percent, duration) is informational — it drives how the frontend renders the result. Slugs are lowercase [a-z0-9_]+, unique per workspace.
Query a metric
POST /api/v1/metrics/{id}/query
Authorization: Bearer <token>
Content-Type: application/json
{
"dims": ["country"],
"filters": [
{ "name": "platform", "op": "eq", "value": "web" }
]
}The metric runs against its connection and returns a typed result:
{
"columns": [{ "name": "value", "dataType": "bigint" }],
"rows": [[12453]],
"totalRows": 1,
"durationMs": 42
}dimensionsJson and filters against the filtersJson whitelist before rendering — request values become bound parameters, never string-concatenated SQL.On dashboards
Add a metric widget to any dashboard. Because the definition is shared, changing the metric once updates every dashboard that references it. Results are cached briefly per (metric, params) to keep boards snappy.