Filtering

Complete guide to Hood Web SDK filtering system: operators, macros, lookup tables, and advanced targeting.
developer

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).

Info

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 in variables from 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 like country is filled automatically
  • URL snapshot (url.*) and referrer snapshot (referrer.*) captured at SDK init
  • Cookies (cookie.*) captured at SDK init
  • Custom variables (var.*) from init/autoconf variables.var
  • Window/document properties (window.*, document.*) — the real browser objects, not SDK internals
  • Lookup tables (lookup.*, lookupr.*) and functions (call.*) registered via variables

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 in c are resolved before evaluation. Missing c throws inside evaluation and the filter fails closed (false).
  • o (string, required): Operator - Comparison method (eq, gt, contains, etc.). Unknown o warns and defaults to ==.
  • m (string, optional): Match value - Value to compare against. Not used by isset, isnset, isempty, isnempty, istrue, isntrue, isfalse, isnfalse.
  • d (string, optional): Default - Falsy fallback. Applied when the resolved check is falsy ('', null, undefined, 0, false). If d is set, cu / cn never run for those values.
  • cu (string, optional): Custom undefined - Used only when the check is still undefined after d (so not if d already replaced it).
  • cn (string, optional): Custom null - Used only when the check is still null after d.
  • ct (string, optional): Custom true - Value to use when check expression returns boolean true (after d / cu / cn).
  • cf (string, optional): Custom false - Value to use when check expression returns boolean false. If d already replaced false, cf never runs.
  • f (string, optional): String formatting - Transforms the final value to lowercase (lc or sc) 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 lowercase
  • uc: Convert to uppercase
  • sc: Currently behaves identically to lc — 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 >= min and <= max. Non-numeric → false.
  • Example: 75 between [50, 100] → ✅ True
  • Usage: { "c": "{{data.age}}", "o": "between", "m": "[25, 65]" }
  • m may 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 undefined or null
  • 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 true or string "true"
  • Example: true istrue → ✅ True
  • Usage: { "c": "{{data.premium}}", "o": "istrue" }

isfalse - Is false

  • Description: Checks if value is boolean false or 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 empty
  • undefined: 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 undefined or null
  • 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 c as a className (token list, no leading dot) on a throwaway <div>, then returns element.matches(m). m is the selector. Non-strings → false.
  • Example: "btn-primary" matchcss ".btn-primary" → ✅ True
  • Example: ".btn-primary" matchcss ".btn-primary" → ❌ False (c must 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. Same c = 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.

Info

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

NamespaceDescriptionExampleUse 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.

No `utm.*`, `partner.*` or `session.*` namespaces
Campaign parameters are reachable through the URL instead: use {{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), or 0 if missing/hidden. Used with visibility triggers and percentage filters.
  • {{call._attr('.selector', 'href')}} — attribute or null.

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') / identitydata.*.

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). If d is set, cu/cn do not run for those values. Example: "d": "free"
  • cu: Custom undefined - Used when the result is still undefined after d. Example: "cu": "unknown"
  • cn: Custom null - Used when the result is still null after d. Example: "cn": "empty"
  • ct: Custom true - Used when result is boolean true. Example: "ct": "yes"
  • cf: Custom false - Used when result is boolean false (after d). Example: "cf": "no"

Example:

{
  "c": "{{data.subscription}}",
  "o": "eq",
  "m": "active",
  "d": "inactive",
  "cu": "unknown",
  "cn": "null"
}

String Formatting (f field):

FormatDescriptionPurposeExample
lcLowercaseTransforms value to lowercase"Hello""hello"
ucUppercaseTransforms value to uppercase"hello""HELLO"
scLowercase (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.

Flat arrays are AND, not OR
A flat filter array requires all filters to pass. If you need OR, you must use the nested group form shown above — otherwise a filter set like three country checks can never match, since a single value cannot equal three different countries at once.

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

  1. Order filters by selectivity - Put most restrictive filters first
  2. Use appropriate operators - isset is faster than eq for existence checks
  3. Cache expensive operations - Use var.* for computed values
  4. 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

  1. Provide defaults - Use d, cu, cn fields for missing data
  2. Validate inputs - Test filters with various data states
  3. Handle edge cases - Consider null, undefined, and empty values
  4. 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 isset operator first
  • Test with simple values: Start with eq operator

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 / identity for user data (data.*)
  • Put var.*, lookup tables, and call.* functions in init/autoconf variables
  • url / cookie / referrer are 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"
}
Only `c` is macro-resolved
Macros are expanded in the 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.