Filters and segmentation
Filters let you select a subset of your data for analysis. Use them to segment users, pick out specific events, and build complex conditions.
What are filters?
A filter is a set of conditions that decides which events or users are included in the analysis. Filters run at the SQL query level (but you never need to know SQL), which keeps execution fast and efficient.
Filter structure
Filters in Metriox have a hierarchical structure:
- Filter
- Logic — AND or OR
- Predicates
- Predicate 1
- Predicate 2
- and so on
Filter logic
Logic defines how the conditions are combined:
- AND — every condition must match
- OR — at least one condition must match
Predicate types
A predicate is a single filter condition. It consists of:
- Type — an event, a property, or a user
- Operator — the comparison operation
- Value — what you compare against
- Negation — NOT (optional)
These four parts form one row in the filter builder. Here is how it looks on the Events page: the NOT toggle, the field, the operator, and the value, with the Add filter type menu adding the next predicate.

Event predicates
Filtering by event name:
Event Name equals "button_click"
NOT Event Name equals "button_click"
Event Name contains "purchase"
Event Name startswith "pay"
Operators for events:
equals— exact match!=— not equalcontains— contains a substringstartswith— starts with (expression line only)
Examples:
| Condition | What matches |
|---|---|
Event Name equals "purchase_completed" | Only events named purchase_completed |
Event Name contains "button" | Events button_click, button_view, share_button, and any other name containing that substring |
NOT Event Name equals "bot_started" | Every event except bot_started |
Property predicates
Filtering by event properties:
plan equals "premium"
NOT plan equals "free"
price > "100"
The available operators depend on the property's data type.
User predicates
These select whole users rather than individual events. In the builder this is one type, User: in the row's first list, choose what to check. The list has two groups, and they answer different questions.
| Group | Question | Example |
|---|---|---|
| Profile (now) | who this person is | "has Telegram Premium", "interface language ru", "arrived through the promo deep link" |
| Activity in the period | what they did in the selected period | "pressed buttons on at least 5 different days", "spent more than 10,000" |
Until something is chosen in the list, the condition is not applied.
Profile (now)
Telegram profile attributes: Telegram Premium, interface language, username, first name, last name, Telegram ID and deep-link parameter. Below them, the "Found on this bot" group lists the other profile properties your bot has.
- The value is today's. The profile keeps only the latest known value, so a "Premium" condition on last month's report selects the people who are Premium now, not then.
- Operators:
equalsandnot equals. First name, last name, username and deep-link parameter also offercontains, because names carry emoji and mixed case. - "Not Premium" and
not equals"Premium" are different selections. "Not Premium" selects only people known not to have Premium.not equals"Premium" adds everyone whose flag was never recorded, which on many bots is most of the audience. - At most 4 profile conditions in one filter. Once there are four, the "Profile" group is disabled in a new row, and the list says why. The "Activity" group stays available.
In the expression box above the builder, such a condition reads:
profile $tg.is_premium = "true"
Activity in the period
This condition first computes a figure per user, then keeps the users who clear the threshold. It has four parts:
| Part | What it sets | Example |
|---|---|---|
| Metric | what to measure per user | Event count, Active days, Sum of a property |
| Counting events named | which events to count | successful_payment |
| Window | measure over the whole period, or on the busiest day | In total / On the busiest day |
| Threshold | operator and number | > 10000 |
Name the event. Without it the condition is not applied: more than 5 of what? Conditions in the main builder do not affect the count. Only events with the name in this field, within the selected period, are counted.
The Sum of a property metric additionally needs a numeric property and a source, for the same reason the Sum metric does: one payment can arrive from two sources, and without pinning to one the total doubles. See Metrics. A Sum threshold may be fractional; the other metrics take whole numbers.
The window separates two different questions. "In total" is the sum across the whole period ("spent
more than 10,000 this month"). "On the busiest day" is the maximum over days: with > it means "spent
more than 1,000 on at least one day", with < it means "under that every day". It is not available for
Active days, which is always 1 within a single day.
This type has no NOT toggle. To find people who never paid, set the threshold to = 0 rather than
NOT around > 0. = 0 selects only people, while NOT would also match group and channel events that have
no user. In the expression box, NOT before a profile condition is refused, and NOT before an activity
condition is accepted but answers that broader question.
Examples:
| Segment | Metric | Counting events named | Window | Threshold |
|---|---|---|---|---|
| Regulars (pressed buttons on at least 5 different days) | Active days | callback_query | In total | >= 5 |
| Paying users | Sum of a property ($tg.total_amount, Bot API) | successful_payment | In total | > 10000 |
| One big purchase | Sum of a property ($tg.total_amount, Bot API) | successful_payment | On the busiest day | > 5000 |
| Never paid | Event count | successful_payment | In total | = 0 |
In the expression box, such a condition reads:
Event Count where "successful_payment" (Total) > 3
The expression line
Above the builder there is a line with the same filter written as text. You can copy it, paste one in, or edit it by hand. Changes apply on Enter or when the cursor leaves the line. If the line cannot be parsed, the reason appears under it and the filter stays as it was.
- An event field is written as
Event Name,Event Type,Source,Origin,TextorUser ID. - A property is its key exactly as the field list shows it:
plan,$tg.chat_type. - A profile condition starts with the word
profile:profile $tg.is_premium = "true". - The value is always quoted, even a number:
price > "100". Write a quote inside a value as\"and a backslash as\\:plan equals "say \"hi\"". - A number uses a dot:
"10.5". A boolean is"true"or"false". ANDandORmay be written in any case, but one line uses only one of them.NOTgoes before the condition:NOT Event Name equals "bot_started".User IDonly takesequalsand!=: an identifier is compared whole, never withcontains.- An activity condition must name its event:
Event Count where "purchase" (Total) >= 2.
The line is a record of what the builder holds, not a separate filter. A condition it cannot write (for example a nested group sent through the API) is shown read-only in the builder, and the line cannot be edited while it is there.
Operators by data type
String
Operators:
equals— exact matchcontains— contains a substringstartswith— starts with (expression line only)!=— not equal. Events that have no such property at all match too, as they do with the NOT toggle.
Examples:
| Condition | What matches |
|---|---|
plan equals "premium" | Only events where plan = "premium" |
button_name contains "buy" | button_name: "buy_now", "buy_premium", "buy_subscription" |
NOT language equals "en" | Every language except English, and events with no language recorded |
Number
Operators:
=— equal to!=— not equal to>— greater than<— less than>=— greater than or equal to<=— less than or equal to
A number is quoted and uses a dot: "10.5", not "10,5".
Examples:
| Condition | What matches |
|---|---|
price > "100" | Price above 100 |
quantity = "1" | Quantity equal to 1 |
age >= "18" | Age 18 and older |
NOT rating < "4.0" | Rating 4.0 and higher, and events with no rating at all |
Dates and times
Filters cannot compare dates yet. A date your bot sends in a property, such as
"2026-03-01T00:00:00Z", reaches the field list as a string, so only string operators apply to it:
equals finds an exact match, but "after 1 March" cannot be asked that way.
For "when" questions use the report period. For "what did this person do in the last N days" use a User → Activity over a period condition.
Boolean
Operators:
equals— equal to (true/false)not equals— not equal to
Examples:
| Condition | What matches |
|---|---|
is_subscriber = "true" | Subscribers only (your own event property) |
email_verified = "false" | Email not confirmed |
NOT has_completed_onboarding = "false" | Users who completed onboarding (true or null) |
Combining conditions
AND logic
Every condition must match at the same time.
Example:
- AND
Event Name equals "purchase_completed"amount > "500"currency equals "RUB"
This selects purchases above 500 roubles.
OR logic
At least one condition must match.
Example:
- OR
Event Name equals "purchase_completed"Event Name equals "subscription_renewed"Event Name equals "trial_started"
This selects any monetization event.
AND and OR together
In the builder the whole filter is joined one way: every condition with AND, or every condition with OR. An OR group cannot be nested inside AND in the builder. Such a filter can be sent through the API; the builder then shows the nested group read-only and keeps it as it is, but cannot edit it.
A common workaround for "one of several events" is a shared fragment of the name:
Event Name contains "click" instead of two conditions joined with OR.
Negation (NOT)
Add NOT to an event or property predicate to invert its condition. User predicates have no NOT toggle in the builder: use the not equals operator or the = 0 and < thresholds instead (see above).
| Condition | What matches |
|---|---|
NOT Event Name equals "bot_started" | Every event except bot_started |
NOT plan equals "free" | Everyone whose plan is not free, including events with no plan at all |
NOT age < "18" | Age 18 and older, and events with no age — the inverse of "younger than 18" |
Filter examples
Simple filters
All purchases:
Event Name equals "purchase_completed"
Users with Telegram Premium:
profile $tg.is_premium = "true"
Premium is a profile attribute, not an event property, so this is a User condition, not a Property one.
Purchases above 1000 roubles:
- AND
Event Name equals "purchase_completed"amount > "1000"
Complex filters
New premium users:
- AND
Event Name equals "subscription_started"plan equals "premium"is_trial = "false"
Button presses by people, not bots:
- AND
Event Name contains "button"NOT is_bot = "true"
Problem transactions:
- AND
Event Name equals "payment_failed"attempt_number > "2"amount >= "500"
User segmentation
Young audience from Russia:
- AND
age < "25"country equals "RU"language equals "ru"
High-value users:
- AND
total_spent > "5000"is_subscriber = "true"Event Count where "successful_payment" (Total) >= 3
Applying filters
Every widget has its own filters:
- "Premium purchases" widget
- Type — Series
- Metric — Events
- Filter
- AND
Event Name equals "purchase_completed"plan equals "premium"
- AND
A filter you need in more than one place can be saved as a segment: apply it in reports and insert it into a broadcast audience without building the conditions again.
Technical details
Compiling to SQL
Filters compile into SQL WHERE conditions.
Filter:
- AND
Event Name equals "purchase_completed"amount > "500"
SQL (conceptually):
WHERE event_name = 'purchase_completed'
AND JSONExtractFloat(props, 'amount') > 500
You do not need to know SQL — Metriox turns your filters into optimized queries automatically.
Performance
Metriox uses ClickHouse to execute queries quickly:
- Indexing on
event_name - Efficient handling of JSON properties
- Parallel query execution
Optimization:
- Filter by event first
- Prefer
equalsovercontainswhere possible - Keep the time range narrow
Best practices
Building filters
- Start by filtering on events
- Then add property conditions
- Use NOT sparingly (it can be slower)
Testing
- Test filters on short time ranges first
- Compare the results against what you expect
- Use tables to inspect the details