looker-filters)
Choosing the right Looker filter mechanism
Looker supports single-field Filter Expressions, multi-field Custom Filters, and LookML SQL/Liquid filter blocks. Pick the simplest mechanism that matches the predicate:
| Filtering need | Mechanism | Syntax | Where it lives |
|---|---|---|---|
Single-field match, list (OR), range, wildcard, null check, or relative/absolute date |
Looker Filter Expression (docs) | Quoted shorthand string per field ("this year", "-Cancelled", "[0, 5]", "-NULL"); multiple fields combine with AND |
measure: { filters: [...] }, explore: { query: { filters: [...] } }, always_filter:, aggregate_table:, NDT & .test.lkml explore_source: { filters: [...] }, .dashboard.lookml, run_inline_query / create_query body["filters"] |
Cross-field comparison (${orders.created_year} = ${users.created_year}) or cross-column OR (status = 'Complete' OR is_override) at query/NDT time |
Custom Filter (looker-expressions) | Boolean Looker expression referencing dimensions only (${orders.created_year} = ${users.created_year}) |
run_inline_query / create_query body["filter_expression"], Explore Custom Filter UI, or NDT explore_source: { expression_custom_filter: ... ;; } |
Reusable cross-column OR inside a LookML measure |
Hidden type: yesno helper + filters: (02-conditional-measures-and-bucketing.md) |
dimension: is_completed_or_override { type: yesno hidden: yes sql: ... ;; } + filters: [is_completed_or_override: "yes"] |
bronze/:table.refinement.lkml or silver/:view_a_:view_b_fields.view.lkml |
| Embed a Filter Expression inside a Custom Field or Custom Filter formula | matches_filter() bridge (docs, looker-expressions) |
matches_filter(${orders.created_date}, `30 days`) (backticks required) |
Custom Dimensions, Custom Measure filters, Custom Filters |
| Mandatory row-level security by user attribute | access_filter: (lookml-explore) |
access_filter: { field: view.field user_attribute: attr_name } (access_grant: takes literal allowed_values, never Filter Expressions) |
.explore.lkml |
| Push Explore runtime filters into an inner derived table | bind_all_filters / bind_filters or Gold Liquid {% condition %} (14-templated-filters-and-parameters.md) |
bind_all_filters: yes on NDT (same filters as outer query) or filter: + {% condition %} + always_filter: in gold/ (asymmetric cohort vs. population) |
gold/:name.view.lkml + gold/explores/:name.explore.lkml (lookml-liquid) |
Looker Filter Expression syntax by data type
In LookML (filters: [...]), always wrap the entire filter expression in double quotes ("...")—especially "NULL", "-NULL", and "NOT NULL". In the Explore UI (matches (advanced)) and SDK body["filters"] values, omit the outer LookML quotes.
1. Strings (type: string)
String matching honors the model's case_sensitive setting and database dialect sensitivity.
| Expression (in LookML quotes) | Meaning | Notes & traps |
|---|---|---|
"Complete" |
Exact match (= 'Complete') |
|
"Socks,Socks & Hosiery" |
Exact OR (IN ('Socks', 'Socks & Hosiery')) |
Comma , means OR. Avoid unintended spaces after , unless matching a leading space. |
"%cancel%" / "FOO%" / "%FOO" |
Contains / starts with / ends with | % matches zero or more characters; _ matches any single character ("_UF"). |
"-Cancelled" |
Not equal (!= 'Cancelled' or IS NULL) |
Leading - negates the token. |
"-Cancelled,-Returned" |
Neither 'Cancelled' nor 'Returned' |
Comma-separated negated terms become AND NOT exclusions. |
"FOO%,-FOOD" |
Starts with 'FOO' and is not 'FOOD' |
Combines positive prefix with - exclusion. |
"-FOO%" / "-%FOO%" |
Does not start with / does not contain 'FOO' |
|
"NULL" / "-NULL" |
IS NULL / IS NOT NULL |
Always use "-NULL" on type: string—writing "NOT NULL" on a string dimension matches the literal string 'NOT NULL'. |
"EMPTY" / "-EMPTY" |
Empty string '' or NULL / non-empty (LENGTH >= 1) |
Use "-EMPTY" for SQL col != '' AND col IS NOT NULL. |
Escaping special characters with ^:
- Escape literal
",%,_,,, or^in LookML filter expressions andmatches (advanced)with^:"^_items","100^%","Santa Cruz^, CA","^^","^"". - Escape a leading
-(or leading space) only when it is the first character:"^-100"(hyphens inside"e-commerce"need no escape). - (Standard Explore UI dropdown filters escape literal commas with
\asSanta Cruz\, CA; LookMLfilters:andmatches (advanced)always use^,.)
2. Dates and times (dimension_group timeframes & type: date*)
| Pattern | Example | Bound semantics & behavior |
|---|---|---|
today / yesterday / tomorrow |
"today" |
Full calendar day (00:00:00 through 23:59:59). Never write "this day" (unsupported—use "today"). |
this {interval} |
"this week", "this month", "this quarter", "this year" |
Full current calendar period (Looker weeks start Monday unless week_start_day is set). |
last {interval} / {n} {interval} ago |
"last month", "1 week ago", "2 years ago" |
Single complete prior period ("2 years ago" in 2026 = all of 2024). |
{n} {interval} / last {n} days |
"7 days", "3 months", "2 years" |
Rolling window including the current partial period ("3 days" = today + prior 2 days). |
{n} {interval} ago for {m} {interval} |
"7 days ago for 7 days" |
Complete prior periods only, excluding today's partial period. |
this {interval} to {interval} |
"this year to second", "this week to day" |
Start of first interval to start of second interval (YTD / WTD). |
after {time} |
"after 2024-01-01" |
Inclusive lower bound (>=): includes 2024-01-01 00:00:00 and later. |
before {time} / before {n} {interval} ago |
"before 2024-01-01", "before 3 days ago" |
Exclusive upper bound (<): strictly before 2024-01-01 00:00:00 (excludes 2024-01-01). |
{time} to {time} |
"2024-01-01 to 2024-07-01" |
Half-open [start, end): includes start, excludes end (2024-01-01 00:00:00 through 2024-06-30 23:59:59). |
{time} for {n} {interval} |
"2024-05-10 for 3 days" |
Inclusive of the full span (2024-05-10 00:00:00 through 2024-05-12 23:59:59). |
{YYYY} / {YYYY-MM} / {YYYY-MM-DD} / {YYYY-Q#} |
"2024", "2024-01", "2024/05/29", "2024-Q4" |
Matches the full year, month, day, or quarter. Always zero-pad hyphenated months/days ("2024-01", never "2024-1", which Looker parses as 2024 - 1 = 2023). |
| Fiscal periods | "FY2024", "FY2024-Q1", "last fiscal quarter", "this fiscal year" |
Requires fiscal_month_offset on the model. |
| Future windows | "next week", "3 days from now", "3 days from now for 2 weeks" |
next supports week, month, quarter, fiscal quarter, year, fiscal year only. |
{day_of_week} |
"Monday", "after Monday" |
On a _date field, matches the most recent Monday; on a _day_of_week field, matches every Monday. |
- Combining date conditions: Join with
,inside one filter forOR("today, 7 days ago"). ForANDbounds that cannot be written as{start} to {end}or{start} for {n}, filter two timeframes of the samedimension_group(created_date: "after 2024-01-01", created_time: "before 2 days ago"). - Timestamp/date
NULLchecks: Rather than filtering${<group>_date}: "NULL"(which appliesDATE()/ timezone casts), define one positivetype: yesnodimension on therawtimeframe (is_sold->sql: ${sold_raw} IS NOT NULL ;;) and filteris_sold: "no"or"yes"(02-conditional-measures-and-bucketing.md).
3. Numbers (type: number, numeric tier / duration, and numeric measure HAVING filters)
| Expression | Meaning | Notes & traps |
|---|---|---|
"5" / "1, 3, 5, 7" |
= 5 / IN (1, 3, 5, 7) |
Comma , or OR joins alternatives ("<25, >65"). |
">100" / ">=30" / "<=10" / "!=5" / "<>5" / "NOT 5" |
Relational comparisons | Works on both numeric dimensions (WHERE) and measures (HAVING, e.g. orders.count: ">=30"). |
">=18 AND <=25" / "5.5 to 10" / "1 to" / "to 10" |
Inclusive range (18 <= x <= 25, >= 1, <= 10) |
Combine ranges with OR: ">=50 AND <=100 OR >=500 AND <=1000". |
"[0, 5]" / "(1, 7)" / "[12, 20)" / "(12, 20]" |
Algebraic interval notation | [ / ] = inclusive; ( / ) = exclusive ("[0, 5]" -> 0 <= x <= 5). |
"(500,)" / "(500, inf)" / "(,10]" / "[0,9],[20,29]" |
Open-ended or union intervals | inf is always open ( / ). |
"NULL" / "NOT NULL" |
IS NULL / IS NOT NULL |
On type: number, use "NOT NULL" (e.g. orders.id: "NOT NULL" to enforce inner-join semantics on a left_outer Explore). |
- Leading-
NOTtrap on numbers: If the first comma-separated item hasNOTand later items do not, Looker negates every item ("NOT 66, 99, 4"meansNOT IN (66, 99, 4), while"NOT >1, <100"negates both into<=1 AND >=100, which compiles toIS NULL). Put positive clauses first (">1 AND <100, NOT 2").
4. Booleans (type: yesno vs. database booleans)
type: yesnofields: Use"yes"or"no"(or"Yes"/"No"infilters:/always_filter:). Enforce positive polarity on field names (is_sold: "no", neveris_unsold: "yes").- Raw boolean columns (non-
yesno): Use"TRUE"or"FALSE".
5. Locations (type: location) & User Attributes
- Locations:
"36.97, -122.03"(exact),"40 miles from 36.97, -122.03"(units must be plural:meters,feet,kilometers,miles—even"within 1 miles of 36.97, -122.03"),"inside box from 72.33, -173.14 to 14.39, -61.70","NULL","-NULL","NOT NULL". - User attributes inside filter strings:
"{{ _user_attributes['region'] }}"or prefixed"sf_{{ _user_attributes['salesforce_username'] }}"in Explorematches (advanced)and.dashboard.lookmlfilters.
Custom Filters (filter_expression & expression_custom_filter:)
Single-field filters: maps always join multiple fields with AND and cannot compare one column against another. Switch to a Custom Filter whenever SQL WHERE needs cross-field comparisons or cross-column OR logic:
- Rules: Custom Filters use looker-expressions syntax, must evaluate to
yesorno, compile into warehouse SQLWHERE, and can reference dimensions only (never measures, totals, positional/pivot functions, or table calculations). - Bridge single-field shorthand with
matches_filter: Wrap Filter Expressions in backticks inside a Custom Filter:(${orders.created_year} = ${users.created_year}) AND (matches_filter(${orders.status}, `Complete,Shipped`) OR ${orders.is_override}) - Where authored:
- SDK query
body["filter_expression"](run_inline_query/create_query): Passed alongside or instead ofbody["filters"](when both are set, Looker combines them withAND). - LookML NDT
expression_custom_filter:insidederived_table: { explore_source: ... }:explore_source: order_items { column: user_id { field: order_items.user_id } column: first_year_revenue { field: order_items.total_sale_price } filters: [order_items.status: "-Cancelled"] expression_custom_filter: ${order_items.created_year} = ${users.created_year} ;; } - Custom Measure
filter_expressioninsidebody["dynamic_fields"](filters a single ad-hoc measure without filtering the whole query).
- SDK query
Using filters in LookML and Looker SDK methods
1. LookML Filtered Measures (bronze/:table.refinement.lkml & silver/*_fields.view.lkml)
measure filters: works only on aggregating measure types (count, count_distinct, sum, average). Never place filters: on type: number—filter the underlying sum/count component measures instead (param-field-filters). Multiple filters: entries combine with AND; for cross-column OR, filter on a hidden: yes type: yesno dimension:
# bronze/orders.refinement.lkml
include: "orders.view.lkml"
view: +orders {
dimension: is_returned {
type: yesno
sql: ${returned_raw} IS NOT NULL ;;
}
dimension: is_ever_returned {
type: yesno
hidden: yes
sql: ${status} = 'Returned' OR ${is_returned} ;;
}
measure: completed_revenue {
type: sum
sql: ${sale_price} ;;
filters: [status: "Complete", created_date: "this year"]
value_format_name: usd
}
measure: returned_order_count {
type: count
filters: [is_ever_returned: "yes"]
}
# Ratio over filtered data: filter atomic sum/count measures, NEVER type: number
measure: completed_order_count {
type: count
filters: [status: "Complete"]
hidden: yes
}
measure: completed_average_order_value {
type: number
sql: 1.0 * ${completed_revenue} / NULLIF(${completed_order_count}, 0) ;;
value_format_name: usd
tags: ["non_additive"]
}
}
2. Explore Quick Start query:, always_filter:, NDTs, and Data Tests
- Quick Start
query:blocks (bronze/explores/orsilver/explores/): Acceptfilters:on both dimensions (WHERE) and measures (HAVING), e.g.filters: [orders.status: "-Cancelled", orders.count: ">=30"]or relative Business Term slices likefilters: [users.created_year: "this year"](agentic-lookml). always_filter:(lookml-explore): Enforces an editable default filter on partitioned Bronze tables or parameterized Gold Explores (always_filter: { filters: [events.created_date: "7 days"] }).- NDT
explore_source:(lookml-view derived_table): Supportsfilters:, dev-onlydev_filters:,bind_all_filters: yes,bind_filters: { from_field: ... to_field: ... }, andexpression_custom_filter:. - Data Tests (
.test.lkml): Bound equality assertions to a deterministic historical slice usingfilters: [orders.created_date: "2024-01-01"](or block syntaxfilters: { field: orders.created_date value: "2024-01-01" }).
3. Looker SDK Query & Dashboard Filter Methods (lkr-code-mode)
When executing or saving queries via run_inline_query(result_format="json", body=...), create_query(body=...) (+ run_query(query_id=..., result_format="json")), or create_query_task(body=..., result_format="json"), the query body dictionary controls filtering through four keys:
Query body key |
Type | Purpose & behavior |
|---|---|---|
"filters" |
dict[str, str] |
Map of "view.field": "filter_expression" combined with AND (WHERE for dimensions/filter fields, HAVING for measures). Omit outer LookML quotes ("-NULL", not "\"-NULL\""). |
"filter_expression" |
str |
Custom Filter written as a boolean looker-expressions formula over dimensions ("${orders.created_year} = ${users.created_year}"). Required when OR spans multiple fields or two fields are compared; ANDed with "filters" when both are set. |
"filter_config" |
None |
Opaque Explore UI filter bar state. Always omit or set "filter_config": None (null in JSON) when creating or modifying queries via SDK—a non-null filter_config overrides "filters". |
"dynamic_fields" |
str (JSON array) |
Optional JSON-encoded list of Custom Fields; custom "measure" entries accept "filter_expression" (Looker expression) or "filters" to scope that single metric without filtering the whole query. |
# Executed via `lkr --dev code-mode sandbox`
import json
body = {
"model": "agentic_thelook",
"view": "orders",
"fields": ["users.country", "orders.count", "orders.total_revenue"],
"filters": {
"orders.status": "-Cancelled",
"users.email": "-NULL",
"orders.count": ">=30",
},
"filter_expression": "${orders.created_year} = ${users.created_year}",
"filter_config": None,
"sorts": ["orders.total_revenue desc"],
"limit": "20",
}
rows = json.loads(run_inline_query(result_format="json", body=body))
- Dashboard filter SDK methods &
.dashboard.lookml: Usecreate_dashboard_filter(body=...)/update_dashboard_filter(dashboard_filter_id=..., body=...)("default_value"takes a Filter Expression string) and wire tiles vialistenoncreate_dashboard_element/update_dashboard_element(orfilters:+listen:in.dashboard.lookml).