metrics
Metrics

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.

http
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

http
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:

json
{
  "columns": [{ "name": "value", "dataType": "bigint" }],
  "rows": [[12453]],
  "totalRows": 1,
  "durationMs": 42
}
Safe by construction: dimensions are matched against 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.