# 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
```
