Skip to main content

Query definition

The query definition is the JSON object MGM uses to describe an analytics question. The MCP tools execute_query, create_query, update_query, and query_metrics take it, and the CLI's mgm queries --definition flag takes the same object. Learn it once and it works everywhere.

{
"metric": "unique_users",
"event_filter": ["purchase_completed"],
"group_by": "date",
"breakdown_by": "platform",
"granularity": "day",
"date_range": "30d",
"exclude_lifecycle_events": true,
"filters": {
"platforms": ["iOS", "Android"],
"prop:plan": { "op": "in", "values": ["pro", "team"] }
}
}

metric is the only required key.

KeyTypeDefault
metricstring, required—
event_filterarray of event namesall events
group_bystringnone, giving a single number
breakdown_bystringnone
granularityday, week, monthday
date_rangestring30d
exclude_lifecycle_eventsbooleanfalse
filtersobject{}

metric​

count_events, unique_users, unique_sessions, or dau.

Unique users, unique sessions, and DAU are deduplicated with probabilistic sketches. On older data where those sketches are incomplete, the result is {"type": "unavailable"} rather than a number that would be wrong. Identity metrics grouped by a custom property are always unavailable, because custom property rollups carry no sketches — group by a built-in dimension, or use count_events.

group_by and breakdown_by​

group_by takes date or any dimension below, or prop:<property_name> for a custom property you send on events.

platform            app_version         app_build_number
os_version environment device_manufacturer
locale timezone country
region city event_name

breakdown_by takes the same values except date, and only works when group_by is date. It splits a time series into one line per value, for example users over time broken down by platform.

date_range​

FormExample
Preset1d, 7d, 30d, 90d, 365d
Customcustom:2026-01-01:2026-03-31
Monthmonth:11, months:11,12
Quarterquarter:4, quarters:1,4
Same period last yearcompare:month:11, compare:quarter:4
Week over weekcompare:wow:4 (2 to 12 weeks)

The query_metrics MCP tool accepts presets and custom: only, up to 366 days per query. It has its own compare: "previous_period" option instead of the seasonal forms. Use execute_query when you need a seasonal range.

filters​

A flat object of dimension keys and custom property keys. Any other key is rejected with a message naming it.

Dimension keys​

KeyAlso accepted as
platforms—
event_names—
environmentsenvironment
app_versionsapp_version
countriescountry
regionsregion
citiescity

Each takes a string or an array of strings. Several values inside one key mean "any of these"; separate keys are combined with AND. Call the get_filters MCP tool, or run mgm dashboard filters, to see the values your project actually has.

Custom property keys​

Prefix the property name with prop:. Four forms, shortest first:

{ "prop:plan": "pro" }
{ "prop:plan": ["pro", "team"] }
{ "prop:plan": { "op": "neq", "value": "free" } }
{ "prop:plan": [{ "op": "contains", "value": "ent" }, { "op": "is_set" }] }

A clause has:

FieldMeaning
opOperator, defaults to eq
typestring (default), number, boolean, datetime, enum
valueSingle value
valuesArray, for in, not_in, and between

Which operators apply depends on type:

typeOperators
anyis_set, is_not_set
string, enumeq, neq, in, not_in, contains, not_contains, starts_with, ends_with
numbereq, neq, in, not_in, gt, gte, lt, lte, between
booleaneq
datetimebefore, after, on_or_before, on_or_after, between

Set type when you want a numeric or chronological comparison. The default string type compares text, so {"op": "gt", "value": 100} without "type": "number" compares "9" as greater than "100".

{ "prop:revenue": { "op": "gt", "type": "number", "value": 100 } }

Text operators are case-insensitive. between uses the first two entries of values as an inclusive range. is_set means the property is present and not an empty string. Clauses inside one prop: key are combined with OR; different prop: keys are combined with AND.

What a prop: filter costs​

A prop: filter is answered from raw events rather than daily aggregates, so it is slower than a dimension filter. Prefer a dimension filter when one covers what you need.

exclude_lifecycle_events​

Set to true to drop the events SDKs track automatically, which all start with $ — see reserved events. Useful when an event count is dominated by app opens.

Results​

QueryResult
No group_bya single number
group_by: "date"a list of {date, value} points
group_by: "date" with breakdown_byone series per breakdown value
group_by on another dimensiona list of {label, value} rows

Funnels and retention are different​

A funnel is a list of steps, not a query definition. Each step is {"event_name": "...", "name": "Display label"}, with an optional list of property filters in a simpler shape:

{
"event_name": "checkout_started",
"name": "Checkout",
"filters": [{ "property": "plan", "op": "eq", "value": "pro" }]
}

Step filters support eq, neq, contains, not_contains, gt, gte, lt, and lte. A funnel needs between 2 and 15 steps.

Retention takes cohort_event, an optional retention_event (omit it to count any event), a cohort_grain of day, week, or month, and retention_days chosen from 1, 2, 3, 7, 14, 21, 30, 60, and 90.