---
title: "Custom Parameter targeting reference"
description: "Every Custom Parameter operator with a worked example, how the Value list and the request value are split, has all of, the absent-parameter matrix, the Expression kind, the Cookie Value dimension with and / or between cookies, and the REST API."
type: "reference"
audience: [network-manager, publisher, advertiser]
keywords: [custom parameter, operators, equals, one of, has all of, substring, expression, javascript expression, list parameter, separator, url decoded, cookie value, multi-cookie, eas_3p_, match mode, any all, rest api targeting]
ui_path: "Account → entity → Targeting / Pre-targeting → Custom Parameter"
---

Fetch the complete documentation index at: https://epom.com/ad-server/docs/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Custom Parameter targeting reference

The tables behind [Target on custom parameters passed in the ad tag](https://epom.com/ad-server/docs/guides/targeting/custom-parameter-targeting.md).
Every comparison is **case-sensitive on the value** and case-insensitive on the key name, and
values are compared as strings unless the operator is numeric.

## The editor

| Control | Shown for | What it does |
|---|---|---|
| **Type** | always | The rule kind: Expression, Value, One of or Substring. Changing it clears the other fields. |
| **Parameter** | Value, One of, Substring | The key to read off the request, with any `cp.` / `cp_` prefix already stripped. Picked from the registered parameters (`name · label`) or typed after **Other parameter…**; a plain text box when nothing is registered. It has an ⓘ only for a list parameter on a Value or One of rule: *Arrives as a list split on ","* on One of, *Arrives as a list: use One of to match one item* on Value. |
| **Operator** | Value, One of, Substring | The comparison; the choices depend on the kind, and the label has no ⓘ. **has all of** appears only for a list parameter and is cleared when the parameter changes to a plain one. |
| **Value** | Value, One of, Substring | One value for Value; a comma-separated list for One of and Substring. The ⓘ beside it says what the picked operator matches, how to separate values and, on a list parameter, previews a match in that parameter's own separator. The placeholder shows the shape (`sport, news, travel`). When the parameter has a registered list of values, One of picks from it and a Value `equals` / `not equals` rule picks one. |
| **Expression** | Expression | A single boolean expression; the ⓘ recaps the syntax. |
| **Include** / **Exclude** | always | Saves the drafted rule as a whitelist or blacklist entry; disabled until the editor is complete. |

## What each kind matches

| Kind | Operator | Matches when | Worked example |
|---|---|---|---|
| **Value** | `equals` | The value is exactly equal | `section` `equals` `sport` matches `cp.section=sport`, not `Sport` |
| **Value** | `not equals` | The value is anything else, including absent | `section` `not equals` `sport` matches `cp.section=news` |
| **Value** | `>` `≥` `<` `≤` | Both sides parse as numbers (dot decimals, `1e3` accepted) and the comparison holds | `age` `≥` `18` matches `cp.age=18` and `cp.age=18.5`, not `cp.age=17` or a missing `age` |
| **One of** | `is one of` | The value exactly equals one entry in the list | `section` `is one of` `sport, news, weather` matches `cp.section=news` |
| **One of** | `is not one of` | The value equals no entry, including absent | the same list rejects `news`, accepts `travel` |
| **One of** | `has all of` | Every entry in the list is an item of the value; other items may be there too. List parameters only | `segments` `has all of` `1, 3` matches `cp.segments=1,2,3,4,5` and `1,2,3`, not `1,4,5` |
| **Substring** | `contains` | The value contains at least one entry as a substring | `path` `contains` `/sport/, /live/` matches `cp.path=/2026/sport/final` |
| **Substring** | `not contains` | The value contains no entry, including absent | the same list rejects that path, accepts `/news/uk` |
| **Expression** | — | The expression evaluates to `true` | [The Expression kind](#the-expression-kind) |

The Value kind's rule value is trimmed when loaded and has no length limit. The Value field also
expands `$name`: `$otherparam` compares one request parameter against another.

## How the Value list is split

**One of** and **Substring** take a list in one Value field. This is the rule's own list; whether
the *request* value is split too is a setting of the parameter — see
[When the request sends several values](#when-the-request-sends-several-values).

- The separator is a **comma only**. A semicolon becomes part of the value. A line break is not a
  separator either: the box is a single line, so a pasted column arrives with its lines run
  together — `345`, `456` on two lines do not become two values. Put commas between them first.
- The whole list is URL-decoded before it is split, then each entry is trimmed and empties are
  dropped. Duplicates collapse — the entries are held as a set. So a typed `+` reads as a space
  and `%2B` as `+`; write a literal `%` as `%25`. A typed list holding a lone `%` (`50%, a+b`)
  fails to decode as a whole, and its `+` then stays a plus.
- **One of**: a backslash makes the next character literal — `\,` is a comma inside the value,
  `\\` a backslash, and a backslash before any other character is dropped.
- **Substring**: splits on commas not preceded by a backslash and **keeps** the backslash, so a
  part written `a\,b` matches the literal text `a\,b`. A Substring part can therefore never hold a
  comma; `%2C` does not help, because the list is decoded before it is split.
- Values **picked** from a parameter's registered list are written in this form for you — `18+`,
  `50%` and `a\b` are stored as `18%2B`, `50%25` and `a\\b` — so each pick counts whole, commas
  and spaces included, and the rule's summary shows them as picked.
- **Substring** URL-decodes the incoming request value too, so `/sport/` matches a request
  carrying `%2Fsport%2F` and vice versa. **Value** and **One of** compare the raw string.

## When the request sends several values

A tag can pack several values into one parameter — `cp.genre=rock,pop`, or a CTV stitcher's
`contentGenre=Comedy,Drama`. Out of the box a **One of** rule compares that value **whole**, so
`genre` `is one of` `pop` does *not* match `rock,pop`. Register the parameter under
[Admin → Custom parameters](https://epom.com/ad-server/docs/guides/admin/custom-parameters.md) and switch on **The request can carry
several values**, choosing its separator — comma, pipe, semicolon or space. From then on every
One of rule on that parameter reads the request value item by item:

| Rule | Request `cp.genre=rock, pop,,Jazz` (items `rock`, `pop`, `Jazz`) |
|---|---|
| Include · `is one of` `pop` | matches — any item in the list is enough |
| Include · `is one of` `jazz` | no match — values stay case-sensitive, `Jazz` ≠ `jazz` |
| Include · `is not one of` `pop` | no match — *no* item may be in the list |
| Include · `has all of` `rock, jazz` | no match — `rock` is an item, `jazz` is not (`Jazz` ≠ `jazz`) |
| Include · `has all of` `rock, pop` | matches — every value is an item; `Jazz` being there too does not matter |
| Exclude · `is one of` `pop` | fires — the request is turned away when any item matches |
| any rule, parameter absent or blank | no items: `is one of` and `has all of` never match, `is not one of` always does |

- Items are trimmed and empty ones ignored: `rock,,pop` and `rock , pop` both read as `rock`, `pop`.
- **has all of** asks for every value at once: `segments` `has all of` `1, 3` matches `1,2,3,4,5`
  and `1,2,3`, not `1,4,5` (`3` is missing). Order does not matter, a repeated item counts once,
  other items may be there. On a parameter split on `|` the request reads `1|2|3|4|5` and the
  editor's ⓘ shows the example in that separator. It is offered only on a list parameter — read
  whole it could only mean "equals" — and the server refuses it elsewhere. There is no negated
  form: add the same rule with **Exclude**.
- A value without the separator is a one-item list, so it matches exactly as before the parameter
  was registered. Only values that contain the separator change.
- The setting belongs to the **parameter**, not to the rule: every One of rule on it, on any
  entity, at any level, splits the same way. The ⓘ beside **Parameter** says so, the ⓘ beside
  **Value** previews a match, and the saved chip ends in **· list**.
- Only **One of** splits. Value, Substring and Expression rules read the whole value — the editor
  points a Value rule on a list parameter at One of — and so does the `$$CUSTOM_PARAM()$$` macro.
  In reports, the Advanced report's custom-parameter filter matches an item the same way, while
  grouping by Custom parameter shows the whole value.
- Ad serving keeps the registered parameters in memory and reloads them every minute.
- A One of value that itself holds the separator (`rock\,pop`) can never equal one item, so that
  rule never matches; the editor warns as you type it and the parameter's admin page lists such
  rules under **Used by**.
- The request value is URL-decoded once, by the server, before it is split: `rock%2Cpop` arrives
  as `rock,pop` and splits; a double-encoded `rock%252Cpop` arrives as `rock%2Cpop` and does not.

## Several rules: any of or all of

Within the dimension, Include rules are combined with **or** — one matching rule is enough —
while the first Exclude rule that matches rejects the request. Rules on **one** parameter are
always alternatives, because a request carries one value for it: `tier equals gold` and
`tier equals silver` mean "tier is gold or silver". Rules on different parameters, every rule on a
list parameter (including a single **has all of**), and every Expression are groups of their own.

The connector between groups is the list's mode: **or ▾** by default, **and ▾** when every group
must match — `( tier=gold or tier=silver ) and genre is one of [pop]`. A list with one group shows
no connector. On a list parameter two One of rules joined by **and** need both items in the
request; one **has all of** rule does the same inside one rule. Click the connector on the
Targeting card, or call `POST …/targeting/match-mode` with `type=CUSTOM`
([And / or between groups](https://epom.com/ad-server/docs/guides/targeting/add-targeting.md#and--or-between-groups)). A rule added later joins
its list's mode; a copied campaign or creative and a target template keep it. The classic UI shows
the mode in the group header and has no control.

## When the request does not carry the parameter

A missing parameter resolves to the **empty string** and the comparison runs normally, so the
negative operators *match*. (Epom can configure a server-side default value for a parameter —
none is set out of the box; where one is, an absent or blank parameter takes it.)

| Kind and operator | Result when absent | As an Include rule | As an Exclude rule |
|---|---|---|---|
| Value `equals` | no match | request is rejected | rule does not fire; request passes |
| Value `>` `≥` `<` `≤` | no match (empty is not numeric) | request is rejected | rule does not fire; request passes |
| One of `is one of` / `has all of` | no match | request is rejected | rule does not fire; request passes |
| Substring `contains` | no match | request is rejected | rule does not fire; request passes |
| Value `not equals` | **match** | request passes | **request is rejected** |
| One of `is not one of` | **match** | request passes | **request is rejected** |
| Substring `not contains` | **match** | request passes | **request is rejected** |

An **Include** rule with a positive operator added before the tag sends the parameter takes the
entity off air on that placement; an **Exclude** rule with a negative operator fires on every
request that omits it. "Exclude sport" is Exclude + `equals`, not Include + `not equals`.

## The Expression kind

Expression is the only kind that can combine several parameters or use `||` and `&&`. It is a
JavaScript expression, evaluated server-side, in which each parameter is written `$key`.

**The substituted value is quoted for you.** `$section` is replaced by `'sport'` — quotes
included, JavaScript-escaped, `''` when missing — so compare against a quoted literal or a bare
number and never wrap the `$key` yourself:

```
$section == 'sport' && $age >= 18
```

For `cp.section=sport&cp.age=21` that becomes `'sport' == 'sport' && '21' >= 18`, which is `true`
— JavaScript coerces the quoted number for `>=`. `'$section' == 'sport'` produces
`''sport'' == 'sport'`, a syntax error.

String and array methods and regular expressions work — though for "any of these values" a One of
rule on a [list parameter](#when-the-request-sends-several-values) does the same without running a
script on every request:

```
$cat.split(",").indexOf("a") >= 0
($a + $b) > 0
($p1 == 1 || $p1 == 'a') && ($p2 >= 12 && $p2 <= 22)
/^en/i.test($lang)
```

- A `$key` name may contain letters, digits, underscores and `$`. Longer names are substituted
  first, so `$aa` is not corrupted by a rule that also mentions `$a`. Key names ignore case.
- `$CLIENT_USER_AGENT` is always available: the request's full User-Agent string, injected by the
  ad server. It is the supported way to test something the dimension list has no picker for:
  `/Android (.*?);/i.exec($CLIENT_USER_AGENT)[1] >= '2.3'`.
- `===` and `!==` are strict: against a quoted literal they behave like `==`, but `$age === 18`
  compares a string with a number and never matches. The placeholder reads
  `$parameter1 === 'value'`; keep `===` for quoted literals and use `==` for numbers.
- An invalid expression is rejected when you add the rule, with *"Please enter valid expression
  (Sample: ($p1==1 || $p1=='a') && ($p2>=12 && $p2<=22))"*. The check is server-side: **Include**
  enables as soon as the box is non-empty.
- An unquoted bare word is an identifier, not a literal: `$section == sport` throws, which is read
  as "could not evaluate" and rejects the request on an Include rule. An expression that returns a
  non-boolean (`$section` alone) passes the save-time check and never matches.
- Each evaluation has a statement budget, generous for a boolean expression; a script that spends
  it (a loop that does not end) is aborted and read as "could not evaluate".
- Results are cached for an hour, keyed on the fully substituted text. Harmless for a pure
  function of the parameters; stale for anything else, such as `new Date()`. Use the Time Range or
  Day and Time dimensions for wall-clock logic.

## Target the show a stitcher describes

A CTV stitcher that calls a VAST tag set up with an
[upstream macro preset](https://epom.com/ad-server/docs/guides/inventory/placement-video.md#tell-epom-which-stitcher-calls-the-tag)
fills four content parameters on the request. They are targetable like any parameter, with no
`cp.` prefix:

| Parameter | Carries | Example rule |
|---|---|---|
| `contentGenre` | the show's genre, as the stitcher names it | **One of** — `is one of` — `Sports, News`, as Include |
| `contentRating` | its age rating, such as `TV-PG` | **One of** — `is one of` — `TV-MA, TV-14`, as Exclude |
| `contentLivestream` | `1` for a live stream, `0` on demand | **Value** — `equals` — `1`, as Include |
| `contentLanguage` | the show's language | **One of** — `is one of` — `en, es`, as Include |

They are read the way buyers receive them in `content.*`: a value the stitcher left as an
unexpanded macro (`{{CONTENT_GENRE}}`, `[player_params.contentGenre]`) counts as not sent, values
are trimmed, and a live flag sent as `true` / `false` reads as `1` / `0`. Genre and rating are
free text, so match the spellings your stitcher sends — a
[troubleshooting task](https://epom.com/ad-server/docs/guides/admin/system-ops.md) on the placement shows the full request URL. A
request without the value, or with the macro unfilled, behaves exactly like one that omits the
parameter ([absent-parameter matrix](#when-the-request-does-not-carry-the-parameter)).

## The persisted variant — Cookie Value

**Cookie Value** is a separate dimension with two fields, **Name** and **Value**, both required
and trimmed. It reads a value that persists on the visitor's browser rather than one on the
current request.

It does **not** read any first-party cookie your own script writes. It reads only cookies Epom
itself set, whose names carry Epom's third-party cookie prefix — `eas_3p_` by default. The
**Name** you type is the part *after* that prefix, so a cookie named `eas_3p_segment` is matched
by a rule on `segment`. Names compare exactly; values compare whole, ignoring case
(`Cart-Abandoner` matches `cart-abandoner`).

Those cookies are written by Epom's cookie-setting endpoints — `/multi-cookie.gif`,
`/multi-cookie.js` and `/multi-cookie.html` — which turn each query parameter into a cookie:

```html
<img src="https://<ad-serving-domain>/multi-cookie.gif?segment=cart-abandoner|30d"
     width="1" height="1" alt="">
```

The value takes the form `value|lifetime|format`:

| Part | Meaning |
|---|---|
| `value` | What the rule's **Value** field is matched against. |
| `lifetime` | `30d` days, `12h` hours, `45m` minutes, `30s` seconds, `1y` years. A bare number means days. **Omitted, the cookie lasts 365 days.** |
| `format` | `PLAIN` (the default) or `BASE64`. |

| | Custom Parameter | Cookie Value |
|---|---|---|
| Where the value lives | the current ad request | a cookie Epom set on the browser |
| Who writes it | the publisher's tag or the placement defaults | Epom's `/multi-cookie.*` endpoints |
| Lifetime | that one request | the cookie's, 365 days by default |
| Rule kinds | four | one — name plus exact value |
| Value comparison | case-**sensitive** | case-**insensitive** |

Everything else matches Custom Parameter: same card, same Include/Exclude, applied immediately. A
Cookie Value rule whose **Name** is empty is skipped at serving time — the editor will not create
one, but a rule imported through the API can be in that state and then silently does nothing.

### Several cookies: any of or all of

The Include rules of one entity form one list and its Exclude rules another. Inside a list, the
values given for **one cookie name** are always alternatives: a browser keeps one value per name,
so `tier` = `gold` and `tier` = `silver` mean "tier is gold or silver". The connector between
**different cookie names** is the list's mode — **or ▾** by default, **and ▾** when every cookie
is needed ([And / or between groups](https://epom.com/ad-server/docs/guides/targeting/add-targeting.md#and--or-between-groups)).

| List | or (default) | and |
|---|---|---|
| Include | the visitor needs one of the listed values of any one cookie | the visitor needs every listed cookie, each with one of its listed values |
| Exclude | one listed value of any cookie keeps the visitor out | the visitor is kept out only when every listed cookie has one of its listed values |

An Include list of `tier` = `gold`, `tier` = `silver` and `region` = `eu` reads
`( tier=gold or tier=silver ) or ▾ region=eu`. Click the connector and it reads
`( tier=gold or tier=silver ) and ▾ region=eu`: a visitor with `tier=silver` and `region=eu`
passes, a visitor with only `tier=gold` does not, and `bronze` + `eu` fails. A list that names one
cookie only (`test` = `1`, `test` = `2`) has a single group and no connector — it means "test is 1
or 2" either way. A cookie added later joins its list the way the list combines now, so a fourth
cookie added to an **and** list is required too. A copied campaign or creative and a target
template keep each list's mode. The classic UI has no control; it reads the list out in the group
header, e.g. `· (tier = gold or silver) and region = eu`. A longer list shows its first three
values and counts the rest (`· segment A or segment B or segment C +47 more`); hover the header for
the whole expression.

A visitor who saw pricing **and** booked a demo **and** started sign-up, in any order, but did not
buy:

1. Fire one pixel per step, each under its own cookie name:

   ```html
   <img src="https://<ad-serving-domain>/multi-cookie.gif?visited_pricing=1|30d" width="1" height="1" alt="">
   <img src="https://<ad-serving-domain>/multi-cookie.gif?visited_demo=1|30d" width="1" height="1" alt="">
   <img src="https://<ad-serving-domain>/multi-cookie.gif?visited_signup=1|30d" width="1" height="1" alt="">
   <img src="https://<ad-serving-domain>/multi-cookie.gif?purchased=1|90d" width="1" height="1" alt="">
   ```

2. Add three Cookie Value rules with **Include** — `visited_pricing` = `1`, `visited_demo` = `1`,
   `visited_signup` = `1` — and click the **or ▾** between them so it reads **and**.
3. Add `purchased` = `1` with **Exclude**.

The list asks which cookies the visitor has, not the order they were set in.

- To require several steps, give each step **its own cookie name**. `step=pricing` followed by
  `step=demo` overwrites the first, and `step` = `pricing` plus `step` = `demo` in one list means
  "step is pricing or demo" — never both.
- Every cookie of an **and** list must still be alive when the visitor reaches the last step. A
  `|1d` cookie on the first step of a week-long funnel quietly drops the visitor.
- **and** does not widen reach. A Cookie Value rule matches only when the browser sends the ad
  server's cookies with the ad request — not on Safari or any iPhone browser, not on Firefox
  across sites, not in private windows, apps or connected TV — and those visitors fail an **and**
  list exactly as they fail a single cookie. With detailed targeting logging on for the ad
  servers, a [troubleshooting task](https://epom.com/ad-server/docs/guides/admin/system-ops.md) records which cookie a rejected request
  was missing, or that it carried no targeting cookies at all.
- A value both included and excluded is flagged as a contradiction while the Exclude list keeps
  out a visitor with that value alone — when it reads **or**, or holds one group only. An **and**
  Exclude list of two groups or more keeps out only a visitor matching all of them, so the shared
  value is no contradiction there; the card flags it only when the Include list asks for every one
  of those groups, and only for values the Exclude list also names, because then nobody gets
  through. The same holds across levels: a campaign's **and** Exclude list does not flag a
  creative that includes one of its cookies. Switching an Exclude list back to **or** checks
  again, and the switch (on the card or over the API) answers with the warning when a pair turns
  into a contradiction.

## API

`{entity}` is `advertiser`, `campaign`, `banner`, `site`, `zone` or `placement`.

**Create a Custom Parameter rule** — `POST /rest-api/{entity}/{id}/targeting/custom/create.do`
with `rule` (`INCLUDE` / `EXCLUDE`), `customValue`, and for the non-expression kinds `customType`,
`operation` and `parameter`. Enum values are the names: `customType` is `EXPRESSION`, `VALUE`,
`ONE_OF` or `SUBSTRING`; `operation` is `EQUALS`, `NOT_EQUALS`, `GREATER`, `GREATER_OR_EQUALS`,
`LESS`, `LESS_OR_EQUALS`, `IN`, `NOT_IN`, `ALL` (*has all of*), `CONTAINS` or `NOT_CONTAINS`
(note the plural `EQUALS`). A name outside that list is refused with a 400 that names the parameter
and lists the accepted values; a missing operation or parameter, an operation the kind does not
support (`CONTAINS` on `VALUE`), and `ALL` on a parameter that is not registered as a list are
400s too. An invalid expression is a 400 whose message starts *Please enter valid customValue
(Sample: …)*.

**Switch a list between or and and** — `POST /rest-api/{entity}/{id}/targeting/match-mode` with
`type` (`CUSTOM`, `COOKIE_VALUE`, `DMP` or `API_JSON_FIELD`), `rule` (`INCLUDE` / `EXCLUDE`) and
`matchMode` (`ANY` / `ALL`). The response carries the type, the rule, the saved `matchMode`, how
many rules changed and, when a value is now both included and excluded, a warning. A missing or
unknown type, rule or matchMode, a type without a mode, or an empty list is a 400; 403 and 404 as
usual. A rule added later joins its list's mode, and every rule of these types reads back with its
`matchMode` and `matchGroup` (the parameter or cookie name).

**Cookie Value** — `POST /rest-api/{entity}/{id}/targeting/cookie-value/create` adds a cookie
rule, which joins its list's mode.

## Permissions

| What | Demand side (advertiser, campaign, creative) | Supply side (site, zone, placement) |
|---|---|---|
| See and use Custom Parameter | **Custom** under the role's Targeting permissions + **Manage Demand Targeting** | **Custom** under Pre-targeting permissions + **Manage Supply Pre-targeting** |
| See and use Cookie Value | **Cookie Value** under Targeting permissions | **Cookie Value** under Pre-targeting permissions |
| Switch or / and | the same, plus the right to manage the type; a reader without it sees plain `and` / `or` | the same |
| Shared entity | **Can View** shows the rules; adding, removing or switching needs **Can Edit** or **Is Owner** | — |

## Related

- [Target on custom parameters passed in the ad tag](https://epom.com/ad-server/docs/guides/targeting/custom-parameter-targeting.md)
- [Add, edit, and remove targeting rules](https://epom.com/ad-server/docs/guides/targeting/add-targeting.md)
- [Admin → Custom parameters](https://epom.com/ad-server/docs/guides/admin/custom-parameters.md)
- [Targeting dimension reference](https://epom.com/ad-server/docs/reference/targeting-dimensions.md)
- [Macros reference — custom parameters](https://epom.com/ad-server/docs/reference/macros.md)
- [Retargeting and audiences](https://epom.com/ad-server/docs/use-cases/retargeting-and-audiences.md)