Filtering
What are Filters?
Filters are the decision-making engine of the Hood Web SDK. They determine whether an action (modal, push notification, tag execution) should run after a trigger fires. Think of filters as conditional logic that evaluates user data, URL and document snapshots, cookies, and custom variables to make targeting decisions.
Filtering Pipeline
Event Occurs → Trigger Fires → Filters Evaluate → Action Executes (if filters pass)
Example: A user scrolls 50% down the page (trigger), the system checks filters you configured (for example a property you set, or {{url.query.utm_source}}), and if they pass, shows a promotional modal (action).
Important Client-Side Limitation
Filters are executed exclusively client-side and are limited to data available to the SDK. This means:
- No server-side data: Filters cannot access server-side user profiles, purchase history, or other backend data
- Limited data scope: Only data you set via
setuserproperties/identity, values invariablesfrom init/autoconf, and browser context captured at SDK init - Real-time only: Filters work with data available at the moment of evaluation, not historical data
- Privacy constraints: Cannot access sensitive server-side information for privacy and security reasons
Available data sources:
- User properties set via SDK methods (
data.*) — only fields you set; nothing likecountryis filled automatically - URL snapshot (
url.*) and referrer snapshot (referrer.*) captured at SDK init - Cookies (
cookie.*) captured at SDK init - Custom variables (
var.*) from init/autoconfvariables.var - Window/document properties (
window.*,document.*) — the real browser objects, not SDK internals - Lookup tables (
lookup.*,lookupr.*) and functions (call.*) registered viavariables
Not available:
- Server-side user profiles
- Complete purchase history
- Backend analytics data
- Historical user behavior
Filter Object Structure
Every filter is a JSON object with specific fields that define what to check, how to check it, and what value to compare against.
Filter Object Fields
c(string, required): Check expression - Variable to evaluate. Macros incare resolved before evaluation. Missingcthrows inside evaluation and the filter fails closed (false).o(string, required): Operator - Comparison method (eq,gt,contains, etc.). Unknownowarns and defaults to==.m(string, optional): Match value - Value to compare against. Not used byisset,isnset,isempty,isnempty,istrue,isntrue,isfalse,isnfalse.d(string, optional): Default - Falsy fallback. Applied when the resolved check is falsy ('',null,undefined,0,false). Ifdis set,cu/cnnever run for those values.cu(string, optional): Custom undefined - Used only when the check is stillundefinedafterd(so not ifdalready replaced it).cn(string, optional): Custom null - Used only when the check is stillnullafterd.ct(string, optional): Custom true - Value to use when check expression returns booleantrue(afterd/cu/cn).cf(string, optional): Custom false - Value to use when check expression returns booleanfalse. Ifdalready replacedfalse,cfnever runs.f(string, optional): String formatting - Transforms the final value to lowercase (lcorsc) or uppercase (uc) before any further processing. Does not affect the matching logic itself
Basic Example (you must set user_segment yourself — it is not automatic):
{
"c": "{{data.user_segment}}",
"o": "eq",
"m": "premium"
}
Advanced Example:
{
"c": "{{data.user_segment}}",
"o": "eq",
"m": "premium",
"d": "free",
"f": "lc"
}
String Formatting Example:
// Without formatting - might fail due to case differences
{ "c": "{{data.user_type}}", "o": "eq", "m": "Premium" }
// With formatting - ensures consistent comparison
{ "c": "{{data.user_type}}", "o": "eq", "m": "premium", "f": "lc" }
// If data.user_type = "PREMIUM", it becomes "premium" before comparison
Field Definitions
Check Expression
Defines the variable or expression to evaluate. This is the “left side” of the comparison.
Usage:
{ "c": "{{data.user_segment}}" }
{ "c": "{{url.pathname}}" }
{ "c": "{{call._dom('.hero')}}" }
Macro Resolution: If the value contains macros like {{data.user_segment}}, they are resolved to their actual values before evaluation. data.* only has fields you set with setuserproperties or identity — the SDK does not fill country (or similar) automatically.
Examples:
- Static value:
"premium" - User data you set:
"{{data.user_segment}}" - URL snapshot:
"{{url.pathname}}" - Built-in helper:
"{{call._dom('.hero')}}"
Operator
Defines how the check value (c) is compared against the match value (m).
Available Operators:
- Equality:
eq,neq - Numeric:
gt,gte,lt,lte,between - String:
contains,startswith,endswith - Boolean/Existence:
isset,isempty,istrue,isfalse - Negated:
not.contains,isnset,isnempty, etc. - Advanced:
matchregex,matchcss
Usage:
{ "o": "eq" } // Equals
{ "o": "gt" } // Greater than
{ "o": "contains" } // Contains substring
Match Value
Defines the value to compare against the check expression result. This is the “right side” of the comparison.
Usage:
{ "m": "DE" } // String value
{ "m": "100" } // Numeric value
{ "m": "[50, 200]" } // Array for 'between' operator
Data Types: Can be string, number, boolean, or array depending on the operator used.
Important: Match values are static - they cannot contain macros like {{data.user_segment}}. Only the check expression (c) supports macro resolution.
m is not required for isset, isnset, isempty, isnempty, istrue, isntrue, isfalse, isnfalse — those operators ignore m.
For between, m may be a real array [25, 65] or a JSON string "[25, 65]" (JSON.parse). The range is inclusive (>= min and <= max).
Default
Falsy fallback for the resolved check. Applied when !check is true: '', null, undefined, 0, and false — not only empty string or null.
If d is set, cu / cn never run for those missing values. ct / cf still replace boolean true / false after d (so if both d and cf are set, d already ate false).
Usage:
{
"c": "{{data.user_segment}}",
"o": "eq",
"m": "premium",
"d": "free"
}
Example: If {{data.user_segment}} is "", null, undefined, 0, or false, the filter uses "free" instead.
Custom Undefined
Used only when the check is still undefined after d. Do not promise cu runs whenever the variable is undefined if d is also present.
Usage:
{
"c": "{{data.last_purchase}}",
"o": "isset",
"cu": "never"
}
Custom Null
Used only when the check is still null after d.
Usage:
{
"c": "{{data.subscription}}",
"o": "eq",
"m": "active",
"cn": "inactive"
}
Custom True
Provides a value when the check expression returns boolean true.
When to use: Converts boolean values to strings for comparison.
Usage:
{
"c": "{{data.newsletter_subscribed}}",
"o": "eq",
"m": "yes",
"ct": "yes" // Use "yes" if newsletter_subscribed is true
}
Example: If {{data.newsletter_subscribed}} is true, the filter will use "yes".
Custom False
Provides a value when the check expression returns boolean false.
When to use: Converts boolean values to strings for comparison.
Usage:
{
"c": "{{data.newsletter_subscribed}}",
"o": "eq",
"m": "no",
"cf": "no" // Use "no" if newsletter_subscribed is false
}
Example: If {{data.newsletter_subscribed}} is false, the filter will use "no".
String Formatting
Transforms the final value before any further processing.
Available Formats:
lc: Convert to lowercaseuc: Convert to uppercasesc: Currently behaves identically tolc— it lowercases the value without converting spaces to underscores
Formatting is applied only when the resolved value is a string.
Usage:
{
"c": "{{data.user_type}}",
"o": "eq",
"m": "premium",
"f": "lc" // Transform to lowercase
}
Example: If {{data.user_type}} returns "PREMIUM", it gets transformed to "premium" before comparison.
Note: This only transforms the value - it doesn’t affect the matching logic itself.
Operators Reference
Operators define how the check value (c) is compared against the match value (m). The SDK supports multiple categories of operators for different data types and comparison needs.
Equality Operators
eq - Equals (exact match)
- Description: Exact match using loose equality
- Example:
"DE" eq "DE"→ ✅ True - Usage:
{ "c": "{{data.user_segment}}", "o": "eq", "m": "premium" }
neq - Not equals
- Description: Values are not equal
- Example:
"US" neq "DE"→ ✅ True - Usage:
{ "c": "{{data.plan}}", "o": "neq", "m": "free" }
Numeric Operators
gt - Greater than
- Description: Value is greater than match value
- Example:
100 gt 50→ ✅ True - Usage:
{ "c": "{{data.age}}", "o": "gt", "m": "18" }
gte - Greater than or equal
- Description: Value is greater than or equal to match value
- Example:
50 gte 50→ ✅ True - Usage:
{ "c": "{{data.age}}", "o": "gte", "m": "18" }
lt - Less than
- Description: Value is less than match value
- Example:
25 lt 50→ ✅ True - Usage:
{ "c": "{{data.score}}", "o": "lt", "m": "100" }
lte - Less than or equal
- Description: Value is less than or equal to match value
- Example:
50 lte 50→ ✅ True - Usage:
{ "c": "{{data.score}}", "o": "lte", "m": "100" }
between - Between range (inclusive)
- Description: Value is
>= minand<= max. Non-numeric →false. - Example:
75 between [50, 100]→ ✅ True - Usage:
{ "c": "{{data.age}}", "o": "between", "m": "[25, 65]" } mmay be a real array[25, 65]or a JSON string"[25, 65]"
Common usage:
{ "c": "{{data.age}}", "o": "gte", "m": "18" }
{ "c": "{{call.getCartTotal()}}", "o": "between", "m": "[50, 200]" }
Note: Numeric operators automatically convert values to numbers. Non-numeric values return false.
String Operators
contains - Contains substring
- Description: Checks if string contains substring (case-insensitive)
- Example:
"Hello World" contains "world"→ ✅ True - Usage:
{ "c": "{{data.email}}", "o": "contains", "m": "@gmail.com" } - Array Support: Also works with arrays - checks if array includes the match value
startswith - Starts with
- Description: Checks if string starts with prefix (case-insensitive)
- Example:
"JavaScript" startswith "java"→ ✅ True - Usage:
{ "c": "{{url.pathname}}", "o": "startswith", "m": "/checkout" }
endswith - Ends with
- Description: Checks if string ends with suffix (case-insensitive)
- Example:
"document.pdf" endswith ".pdf"→ ✅ True - Usage:
{ "c": "{{data.filename}}", "o": "endswith", "m": ".pdf" }
Boolean & Existence Operators
isset - Is defined
- Description: Checks if value is not
undefinedornull - Example:
"value" isset→ ✅ True - Usage:
{ "c": "{{data.email}}", "o": "isset" }
isempty - Is empty
- Description: Checks if value is empty string, array, or object
- Example:
"" isempty→ ✅ True - Usage:
{ "c": "{{data.cart}}", "o": "isempty" }
istrue - Is true
- Description: Checks if value is boolean
trueor string"true" - Example:
true istrue→ ✅ True - Usage:
{ "c": "{{data.premium}}", "o": "istrue" }
isfalse - Is false
- Description: Checks if value is boolean
falseor string"false" - Example:
false isfalse→ ✅ True - Usage:
{ "c": "{{data.subscribed}}", "o": "isfalse" }
Usage:
{ "c": "{{data.user_id}}", "o": "isset" }
{ "c": "{{data.newsletter}}", "o": "istrue" }
{ "c": "{{data.cart_items}}", "o": "isempty" }
Empty Check Details:
- Strings:
trim() === "" - Arrays:
length === 0 - Objects:
Object.keys().length === 0 null: considered emptyundefined: not considered empty
Negated Operators
All string and boolean operators have negated versions:
not.contains - Does not contain
- Description: Checks if string does NOT contain substring
- Example:
"Hello" not.contains "World"→ ✅ True - Usage:
{ "c": "{{data.email}}", "o": "not.contains", "m": "@competitor.com" }
not.startswith - Does not start with
- Description: Checks if string does NOT start with prefix
- Example:
"file.txt" not.startswith "doc"→ ✅ True - Usage:
{ "c": "{{url.pathname}}", "o": "not.startswith", "m": "/admin" }
not.endswith - Does not end with
- Description: Checks if string does NOT end with suffix
- Example:
"image.jpg" not.endswith ".png"→ ✅ True - Usage:
{ "c": "{{data.filename}}", "o": "not.endswith", "m": ".tmp" }
isnset - Is not set
- Description: Checks if value is
undefinedornull - Example:
undefined isnset→ ✅ True - Usage:
{ "c": "{{data.optional}}", "o": "isnset" }
isnempty - Is not empty
- Description: Checks if value is NOT empty
- Example:
"Hello" isnempty→ ✅ True - Usage:
{ "c": "{{data.cart}}", "o": "isnempty" }
isntrue - Is not true
- Description: Checks if value is NOT boolean
true - Example:
false isntrue→ ✅ True - Usage:
{ "c": "{{data.verified}}", "o": "isntrue" }
isnfalse - Is not false
- Description: Checks if value is NOT boolean
false - Example:
true isnfalse→ ✅ True - Usage:
{ "c": "{{data.active}}", "o": "isnfalse" }
Advanced Operators
matchregex - Regex match (case-sensitive)
- Description: Tests value against regular expression pattern
- Example:
"[email protected]" matchregex "^[^@]+@[^@]+\\.[^@]+$"→ ✅ True - Usage:
{ "c": "{{data.email}}", "o": "matchregex", "m": "^[^@]+@[^@]+\\.[^@]+$" }
matchregexi - Regex match (case-insensitive)
- Description: Tests value against regular expression pattern (case-insensitive)
- Example:
"[email protected]" matchregexi "^[^@]+@[^@]+\\.[^@]+$"→ ✅ True - Usage:
{ "c": "{{data.email}}", "o": "matchregexi", "m": "^[^@]+@[^@]+\\.[^@]+$" }
not.matchregex - Regex no match (case-sensitive)
- Description: Tests that value does NOT match regex pattern
- Example:
"invalid" not.matchregex "^[^@]+@[^@]+\\.[^@]+$"→ ✅ True - Usage:
{ "c": "{{data.input}}", "o": "not.matchregex", "m": "^[0-9]+$" } - Note: this negation uses the dotted form, while the case-insensitive variant below does not
notmatchregexi - Regex no match (case-insensitive)
- Description: Tests that value does NOT match regex pattern (case-insensitive)
- Example:
"invalid" notmatchregexi "^[^@]+@[^@]+\\.[^@]+$"→ ✅ True - Usage:
{ "c": "{{data.input}}", "o": "notmatchregexi", "m": "^[0-9]+$" }
matchcss - Class name vs CSS selector
- Description: Sets
cas a className (token list, no leading dot) on a throwaway<div>, then returnselement.matches(m).mis the selector. Non-strings →false. - Example:
"btn-primary" matchcss ".btn-primary"→ ✅ True - Example:
".btn-primary" matchcss ".btn-primary"→ ❌ False (cmust not include the dot) - Usage:
{ "c": "{{data.user_class}}", "o": "matchcss", "m": ".premium-user" }
not.matchcss - Class name does not match selector
- Description: Negation of
matchcss. Samec= class tokens,m= selector. - Example:
"btn-secondary" not.matchcss ".btn-primary"→ ✅ True - Usage:
{ "c": "{{data.element_class}}", "o": "not.matchcss", "m": ".disabled" }
Safety: Regex patterns are checked with a small heuristic (isSafeRegex). That is not a ReDoS guarantee.
Macros and Data Sources
Macros allow you to access dynamic data from various sources using the {{namespace.property}} syntax. They’re resolved at runtime in the c field only. Recursion depth for macros is 4.
Client-Side Data Only
Remember: All macro data sources are client-side only. The SDK can only access data that has been explicitly made available in the browser context through SDK methods or browser APIs. Server-side data, complete user profiles, or backend analytics are not accessible to filters.
Data Namespaces
Available Data Sources
| Namespace | Description | Example | Use Case |
|---|---|---|---|
data.* | User properties you set via setuserproperties or identity | {{data.user_segment}} | User segmentation |
var.* | Variables from init/autoconf variables.var | {{var.cart_total}} | Configured session data |
cookie.* | Cookies snapshotted at SDK init | {{cookie.session_id}} | Session tracking |
window.* | Real window properties | {{window.innerWidth}} | Viewport / location |
document.* | Document properties | {{document.title}} | Page context |
url.* | URL snapshot at SDK init | {{url.pathname}} | Page targeting |
referrer.* | Referrer snapshot at SDK init | {{referrer.host}} | Traffic source |
call.* | md.fn then window[name] | {{call._dom('.hero')}} | Custom / built-in helpers |
lookup.* | Static lookup table (variables.ts) | {{lookup.currencies.DE}} | Value mapping |
lookupr.* | Regex lookup table (variables.tr) | {{[email protected]}} | Pattern mapping |
These ten namespaces are the complete set the macro resolver accepts. Any other prefix is left unresolved and the filter will compare against the literal macro text.
{{url.query.utm_source}}, {{url.query.utm_campaign}}, and so on. Partner IDs and session counters are collected by the SDK but are not exposed to the filter engine.Examples:
{ "c": "{{data.user_segment}}", "o": "eq", "m": "premium" }
{ "c": "{{window.innerWidth}}", "o": "gte", "m": "768" }
{ "c": "{{url.pathname}}", "o": "contains", "m": "/checkout" }
URL, referrer, and cookie are a snapshot at SDK init. Parts: full, href, origin, protocol, host, hostname, port, pathname, hash, query. Use {{url.pathname}} and {{url.query.<param>}} (hash pairs are merged into the same query object). SPA pageView keeps the init-time snapshot.
Macro Resolution Example:
// Filter definition — user_segment must have been set by you
{ "c": "{{data.user_segment}}", "o": "eq", "m": "premium" }
// At runtime, if data.user_segment = "premium", the filter becomes:
{ "c": "premium", "o": "eq", "m": "premium" }
// Result: ✅ True
Window and document
{{window.*}} and {{document.*}} read the real browser objects ({{window.innerWidth}}, {{window.location.hostname}}, …). Put device or language values you need in filters on data.* or variables.var.
Custom Functions
Function Calls
call.* checks md.fn[name] first, then window[name]. Register functions on init/autoconf via variables.fn (function name → function source string). Existing keys are not overwritten.
Built-in helpers (already on md.fn):
{{call._dom('.selector')}}— visible area % (0–100), or0if missing/hidden. Used with visibility triggers and percentage filters.{{call._attr('.selector', 'href')}}— attribute ornull.
Syntax: {{call.functionName(arg1, arg2)}}
Example:
{ "c": "{{call._dom('.hero')}}", "o": "gte", "m": "50" }
{ "c": "{{call._attr('a.cta', 'href')}}", "o": "contains", "m": "/signup" }
{ "c": "{{call.getCartTotal()}}", "o": "gte", "m": "100" }
Register extra functions in init/autoconf (not a filter object):
{
"variables": {
"fn": {
"getCartTotal": "function () { return window.cart ? window.cart.total : 0; }"
}
}
}
window[functionName] is an additional lookup. Return type is not enforced.
Lookup Tables
Lookup tables are configured on init/autoconf under variables.ts (static) and variables.tr (regex), not as a filter object. registerFilterContext also accepts fn, ts, tr, and var. Existing keys are not overwritten.
Lookup keys in c must be static path segments. Nested macros in the path ({{lookup.currencies.{{data.country}}}}) do not work — the macro regex cannot parse {{ inside the path. Put macros in table values, not in the path.
Static Tables (variables.ts)
Syntax: {{lookup.TABLE_NAME.KEY}}
{
"variables": {
"ts": {
"currencies": {
"DE": "EUR",
"US": "USD",
"GB": "GBP",
"FR": "EUR"
},
"regions": {
"DE": "Europe",
"US": "North America",
"GB": "Europe"
},
"contactTable": {
"primaryEmail": "[email protected]"
}
}
}
}
Usage in Filters (static keys only):
{ "c": "{{lookup.currencies.DE}}", "o": "eq", "m": "EUR" }
{ "c": "{{lookup.regions.DE}}", "o": "eq", "m": "Europe" }
Nested object paths work: {{lookup.myTable.level1.level2.value}}. Nested values inside the table may themselves contain macros, for example "email": "{{lookup.contactTable.primaryEmail}}" then {{lookup.myTable.contactInfo.email}}.
Regex Tables (variables.tr)
Syntax: {{lookupr.TABLE_NAME.KEY}} — the key after tableName. is a static string that is tested against the table’s regex patterns. First matching pattern wins; order matters.
{
"variables": {
"tr": {
"user_agents": {
".*Chrome.*": "chrome",
".*Firefox.*": "firefox",
".*Safari.*": "safari"
},
"email_domains": {
".*@gmail\\.com$": "google",
".*@outlook\\.com$": "microsoft",
".*@yahoo\\.com$": "yahoo"
}
}
}
}
Usage in Filters:
{ "c": "{{[email protected]}}", "o": "eq", "m": "google" }
Transient variables (variables.var)
Set var.* from init/autoconf:
{
"variables": {
"var": { "cart_total": 0, "geo": {} },
"ts": {},
"tr": {},
"fn": {}
}
}
User profile fields remain Hood('setuserproperties') / identity → data.*.
If autoconf includes an og header, it is copied to variables.var.geo → {{var.geo.<key>}} (keys from the header, not a documented country field).
Advanced Filter Features
Value Transformations
Custom Value Handling
Filters support multiple transformation options for handling different value states:
d: Default - Used when the check is falsy ('',null,undefined,0,false). Ifdis set,cu/cndo not run for those values. Example:"d": "free"cu: Custom undefined - Used when the result is stillundefinedafterd. Example:"cu": "unknown"cn: Custom null - Used when the result is stillnullafterd. Example:"cn": "empty"ct: Custom true - Used when result is booleantrue. Example:"ct": "yes"cf: Custom false - Used when result is booleanfalse(afterd). Example:"cf": "no"
Example:
{
"c": "{{data.subscription}}",
"o": "eq",
"m": "active",
"d": "inactive",
"cu": "unknown",
"cn": "null"
}
String Formatting (f field):
| Format | Description | Purpose | Example |
|---|---|---|---|
lc | Lowercase | Transforms value to lowercase | "Hello" → "hello" |
uc | Uppercase | Transforms value to uppercase | "hello" → "HELLO" |
sc | Lowercase (alias) | Currently behaves identically to lc — does not convert spaces to underscores | "Hello" → "hello" |
Practical Example:
{
"c": "{{data.user_type}}",
"o": "eq",
"m": "premium",
"f": "lc"
}
// If data.user_type = "PREMIUM", it gets transformed to "premium" before comparison
// The matching logic still compares "premium" == "premium"
Multiple Filters
Filter Arrays
Multiple filters can be combined using arrays. A flat array uses AND logic — every filter must pass for the action to execute.
Example (AND):
{
"filters": [
{ "c": "{{data.user_segment}}", "o": "eq", "m": "premium" },
{ "c": "{{url.pathname}}", "o": "contains", "m": "/checkout" },
{ "c": "{{data.age}}", "o": "gte", "m": "18" }
]
}
// Action executes only when every filter passes (you must set user_segment and age)
To express OR, nest the filters one level deeper. Each inner array is a group evaluated with AND, and the action executes if at least one group passes.
Example (OR):
{
"filters": [
[{ "c": "{{url.query.utm_source}}", "o": "eq", "m": "google" }],
[{ "c": "{{url.query.utm_source}}", "o": "eq", "m": "facebook" }]
]
}
// Action executes if utm_source is google or facebook
Groups can mix both: [[a, b], [c]] means (a AND b) OR c.
Practical Examples
Understanding Data Limitations
What You CAN Filter On
// ✅ Available - Data set via SDK
{ "c": "{{data.user_segment}}", "o": "eq", "m": "premium" }
// ✅ Available - Real window property
{ "c": "{{window.innerWidth}}", "o": "gte", "m": "768" }
// ✅ Available - URL snapshot (init time)
{ "c": "{{url.query.utm_source}}", "o": "eq", "m": "google" }
{ "c": "{{url.pathname}}", "o": "startswith", "m": "/checkout" }
// ✅ Available - Document
{ "c": "{{document.title}}", "o": "contains", "m": "Sale" }
// ✅ Available - variables.var from init/autoconf
{ "c": "{{var.cart_total}}", "o": "gt", "m": "100" }
What You CANNOT Filter On
// ❌ Not available - Server-side data
{ "c": "{{server.user_purchase_history}}", "o": "gt", "m": "5" }
// ❌ Not available - Backend analytics
{ "c": "{{analytics.lifetime_value}}", "o": "gt", "m": "1000" }
// ❌ Not available - Complete user profile
{ "c": "{{profile.subscription_tier}}", "o": "eq", "m": "enterprise" }
// ❌ Not available - Historical data
{ "c": "{{history.last_login_days}}", "o": "lt", "m": "7" }
Workaround: Set relevant data via SDK methods before filtering:
// Object form is valid
Hood('setuserproperties', {
user_segment: 'premium',
subscription_tier: 'enterprise',
cart_total: window.cart?.total || 0,
});
Put transient values in init/autoconf variables.var.
E-commerce Targeting
Cart Abandonment Recovery
Requires you to set has_completed_checkout (and any other data.* fields). getCartTotal must exist on window or in variables.fn.
{
"triggers": [{ "type": "beforeunload" }],
"filters": [
{ "c": "{{call.getCartTotal()}}", "o": "gt", "m": "0" },
{ "c": "{{data.has_completed_checkout}}", "o": "isfalse" },
{ "c": "{{url.pathname}}", "o": "startswith", "m": "/checkout" }
]
}
User Segmentation
Target premium users with exclusive offers (user_tier and last_purchase must be set by you):
{
"triggers": [{ "type": "scroll", "config": { "vertical": 75 } }],
"filters": [
{ "c": "{{data.user_tier}}", "o": "eq", "m": "premium" },
{ "c": "{{data.last_purchase}}", "o": "isset" },
{ "c": "{{lookup.regions.DE}}", "o": "eq", "m": "Europe" }
]
}
Geographic Targeting
The SDK does not fill data.country. Autoconf og header, if present, is copied to variables.var.geo → {{var.geo.<key>}} (header keys, not a documented country field). Otherwise set properties yourself.
{
"triggers": [{ "type": "load" }],
"filters": [
{ "c": "{{lookup.currencies.DE}}", "o": "eq", "m": "EUR" },
{ "c": "{{var.geo.country}}", "o": "eq", "m": "DE" }
]
}
The var.geo.country line only works if autoconf (or variables.var) actually provided that key.
Mobile-Specific Offers
Device type is not a filter namespace. Set it yourself, or use a window helper:
{
"triggers": [{ "type": "timer", "config": { "time": 10000 } }],
"filters": [
{ "c": "{{data.device}}", "o": "eq", "m": "mobile" },
{ "c": "{{data.has_app}}", "o": "isfalse" }
]
}
data.device and data.has_app must be set by you (for example Hood('setuserproperties', 'device', …)).
Behavioral Targeting
Engagement-Based Targeting
page_views, time_on_site, and bounce_rate are not filled by the SDK. These filters only work if the page set them:
{
"triggers": [{ "type": "scroll", "config": { "vertical": 90 } }],
"filters": [
{ "c": "{{data.page_views}}", "o": "gte", "m": "5" },
{ "c": "{{data.time_on_site}}", "o": "gte", "m": "300" }
]
}
Return Visitor Targeting
Target returning users with personalized content (last_visit and newsletter_subscribed must be set by you):
{
"triggers": [{ "type": "pageView" }],
"filters": [
{ "c": "{{data.last_visit}}", "o": "gte", "m": "7" },
{ "c": "{{data.newsletter_subscribed}}", "o": "isfalse" }
]
}
The SDK’s own visit counter is not exposed to the filter engine, so returning-visitor targeting has to rely on a value you set yourself through setuserproperties (as data.last_visit does here) or on a custom variable.
Best Practices
Performance Optimization
Efficient Filtering
- Order filters by selectivity - Put most restrictive filters first
- Use appropriate operators -
issetis faster thaneqfor existence checks - Cache expensive operations - Use
var.*for computed values - Limit regex complexity - Keep regex patterns simple and safe
Example:
{
"filters": [
{ "c": "{{data.user_id}}", "o": "isset" },
{ "c": "{{url.pathname}}", "o": "startswith", "m": "/checkout" },
{ "c": "{{data.email}}", "o": "matchregex", "m": "^[^@]+@[^@]+\\.[^@]+$" }
]
}
Error Handling
Robust Filter Design
- Provide defaults - Use
d,cu,cnfields for missing data - Validate inputs - Test filters with various data states
- Handle edge cases - Consider null, undefined, and empty values
- Monitor performance - Watch for slow regex or complex lookups
Example:
{
"c": "{{data.user_segment}}",
"o": "eq",
"m": "premium",
"d": "free", // Default for empty values
"cu": "unknown", // Handle undefined
"cn": "null" // Handle null
}
Troubleshooting
Common Issues
Debugging Filters
Filter not matching:
- Check macro syntax:
{{data.property}}not{data.property} - Verify data exists: Use
issetoperator first - Test with simple values: Start with
eqoperator
Performance issues:
- Avoid complex regex patterns
- Limit nested macro calls
- Use
var.*for expensive computations
Data:
- Ensure data is set before filter evaluation
- Use
setuserproperties/identityfor user data (data.*) - Put
var.*, lookup tables, andcall.*functions in init/autoconfvariables url/cookie/referrerare snapshots at SDK init
Example Debug Filter:
{
"c": "{{data.debug_info}}",
"o": "eq",
"m": "expected_value",
"d": "no_user_id",
"cu": "no_data",
"cn": "null_data"
}
c (check) field only. Putting {{data.user_id}} in m compares against that literal text rather than the value, so match values must be static. To compare two dynamic values, resolve one of them into a variable first.