# Guide to targeting with JEXL For a more in-depth explanation of JEXL syntax you can read the [Normady project docs](https://mozilla.github.io/normandy/user/filters.html?highlight=jexl). ## How to write JEXL targeting expressions A message needs to contain the `targeting` property (JEXL string) which is evaluated against the provided attributes. Examples: ```javascript { "id": "7864", "content": {...}, // simple equality check "targeting": "usesFirefoxSync == true" } { "id": "7865", "content": {...}, // using JEXL transforms and combining two attributes "targeting": "usesFirefoxSync == true && profileAgeCreated > '2018-01-07' | date" } { "id": "7866", "content": {...}, // targeting addon information "targeting": "addonsInfo.addons['activity-stream@mozilla.org'].name == 'Activity Stream'" } { "id": "7866", "content": {...}, // targeting based on time "targeting": "currentDate > '2018-08-08' | date" } ``` ## Available JEXL transforms and operators Firefox extends standard JEXL with additional transforms and a binary operator, defined in [`FilterExpressions.sys.mjs`](https://searchfox.org/mozilla-central/rev/907db2c22743f1b24496198b10a3dca4085cfb08/toolkit/components/utils/FilterExpressions.sys.mjs#39-52). Transforms are applied using the pipe operator (`|`), e.g. `someValue | transformName`. ### `date` Converts a date string into a `Date` object. ```java currentDate | date > "2025-05-06" | date ``` ### `stableSample` Hashes the input and returns `true` if the hash falls below the given rate (0.0–1.0). ```java // Target approximately 10% of users, consistently clientID | stableSample(0.1) ``` ### `bucketSample` Hashes the input and returns `true` if it falls within the given bucket range (`start` to `start + count`) out of `total` buckets. The range wraps around if it exceeds `total`, so `bucketSample(70, 50, 100)` checks buckets 70–99 and 0–19. ```java // Target 500 out of 10,000 buckets userId | bucketSample(0, 500, 10000) ``` ### `preferenceValue` Returns the current value of a Firefox preference. Supports `string`, `integer`, and `boolean` preference types. Returns `defaultValue` if the preference does not exist, and throws if the preference exists but is of an unrecognised type. ```java "browser.newtabpage.enabled" | preferenceValue "some.pref" | preferenceValue(true) ``` ### `preferenceIsUserSet` Returns `true` if the user has explicitly changed the preference from its default value. A preference reset to its default value is considered *not* user-set, even if the current value matches what the user previously set. ```java "browser.newtabpage.enabled" | preferenceIsUserSet ``` ### `preferenceExists` Returns `true` if the preference exists with any valid type (`string`, `integer`, or `boolean`). Returns `false` for preferences with an invalid or unrecognised type. ```java "some.optional.pref" | preferenceExists ``` ### `preferenceIsLocked` Returns `true` if the preference is locked (for example, by an enterprise policy), meaning its value cannot be changed. Returns `false` for unlocked or nonexistent preferences. Useful to avoid offering an action that would silently no-op because the preference it sets is locked. ```java "browser.startup.page" | preferenceIsLocked ``` ### `keys` Returns an array of the enumerable keys of an object, or `undefined` if the input is not an object. ```java providerCohorts | keys ``` ### `length` Returns the length of an array, or `undefined` if the input is not an array. ```java topFrecentSites | length > 5 ``` ### `mapToProperty` Given an array of objects, returns a new array of a single named property extracted from each element. ```java topFrecentSites | mapToProperty("host") ``` ### `intersect` Returns the elements present in both arrays, or `undefined` if either argument is not an array. Unlike the transforms above, this is used as an **infix operator** rather than with a pipe. ```java topFrecentSites | mapToProperty("host") intersect ["amazon.com", "ebay.com", "etsy.com"] ``` ### `regExpMatch` Matches a string against a regular expression. Returns an array of matches, or `null` if there are none. Accepts an optional flags argument. ```java currentURL | regExpMatch("^https://example\\.com/") currentURL | regExpMatch("^https://example\\.com/", "i") ``` ### `versionCompare` Compares two version strings. Returns `0` if equal, a negative number if the left is lower, or a positive number if the left is higher. ```java version | versionCompare("120.0") >= 0 ```