From fca7cdd19e23febb3a4e1bbd5d6b83a0f7fc4793 Mon Sep 17 00:00:00 2001 From: Ron Cohen Date: Mon, 7 Sep 2026 13:40:44 +0200 Subject: [PATCH 1/4] Document array attribute targeting and contextJson transport --- SUMMARY.md | 1 + api/public-api/README.md | 62 ++++++++++++---- api/public-api/public-api-reference.md | 9 +++ product-handbook/array-attributes.md | 73 +++++++++++++++++++ product-handbook/concepts/filter.md | 4 + product-handbook/concepts/targeting-rules.md | 8 +- .../feature-targeting-rules.md | 1 + 7 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 product-handbook/array-attributes.md diff --git a/SUMMARY.md b/SUMMARY.md index 5749683..6739bce 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -57,6 +57,7 @@ * [Access rules](product-handbook/feature-rollouts/feature-targeting-rules.md) * [Flag clean-up and archival](product-handbook/feature-clean-up-and-archival-beta/README.md) * [AI code clean-up](product-handbook/feature-clean-up-and-archival-beta/ai-code-clean-up-beta.md) +* [Array attributes](product-handbook/array-attributes.md) * [Remote config](product-handbook/remote-config.md) * [Type safety](product-handbook/type-safety.md) * [Team permissions](product-handbook/team-permissions.md) diff --git a/api/public-api/README.md b/api/public-api/README.md index 462b94d..e7497d1 100644 --- a/api/public-api/README.md +++ b/api/public-api/README.md @@ -104,24 +104,31 @@ This endpoint retrieves a list of flag values evaluated for a particular user or The endpoint is a `GET` request to ensure that the request can be completed without a `CORS Preflight` request to reduce latency. {% endhint %} -The context must be flattened and provided as query parameters. For instance, given the following nested object: - -```typescript -{ - company: { - id: 42, - }, - user: { - id: 99, - }, -} +Send the context as JSON in a single URL-encoded `contextJson` query parameter. This preserves [array-valued attributes](../../product-handbook/array-attributes.md): + +```javascript +const context = { + company: { id: "42", entitlements: ["reports", "exports"] }, + user: { id: "99", roles: ["admin", "editor"] }, + other: { tags: ["beta"] }, +}; +const query = new URLSearchParams({ + publishableKey: "", + contextJson: JSON.stringify(context), +}); +const response = await fetch(`https://front.reflag.com/features/evaluated?${query}`); +const flags = await response.json(); ``` -It needs to be flattened out into the following form: `context.company.id=42&context.user.id=99` . +`contextJson` accepts optional `user`, `company`, and `other` objects. User/company IDs must be strings or numbers, not arrays. Attribute values can be strings, numbers, booleans, null, arrays, or objects containing those leaf values. One object level within an attribute is allowed, such as `user.profile.roles`; deeper object nesting outside arrays is rejected. Objects and nested arrays inside arrays are opaque JSON-encoded elements, not individually addressable paths. -#### Example +The decoded JSON parameter is limited to 16 KiB by default; HTTP servers or proxies may impose a smaller URL limit. Invalid JSON, invalid context shapes, duplicate `contextJson` parameters, or combining `contextJson` with `context`/`context.*` parameters returns a validation error. + +The older dotted scalar format, such as `context.company.id=42&context.user.id=99`, remains supported. Do not encode arrays as `context.user.roles.0=admin`; use `contextJson` instead. -
GET https://front.reflag.com/features/enabled?context.company.id=42&context.user.id=99&publishableKey=pub_prod_Cqx4DGo1lk3Lcct5NHLjWy
+#### Example using the older scalar format
+
+
GET https://front.reflag.com/features/evaluated?context.company.id=42&context.user.id=99&publishableKey=pub_prod_Cqx4DGo1lk3Lcct5NHLjWy
 
{% code title="Response" %} @@ -143,9 +150,28 @@ It needs to be flattened out into the following form: `context.company.id=42&con Reflag utilizes attributes from the `company` endpoint to identify which features are enabled for specific companies. Ensure all `company` attributes referenced in the `context` are also provided through the `company` endpoint. {% endhint %} +#### Evaluation diagnostics + +Flag results may include `evaluationErrors`. An encountered unsupported array operation makes its entire targeting rule fail to match; it does not cause an HTTP error or prevent other rules from matching. Missing fields use code `MISSING_CONTEXT_FIELD`; unsupported array operators use `UNSUPPORTED_ARRAY_OPERATOR`. Config evaluation can report its own errors in `config.evaluationErrors`. + +```json +{ + "evaluationErrors": [ + { + "code": "UNSUPPORTED_ARRAY_OPERATOR", + "field": "user.roles", + "operator": "GT", + "message": "Operator GT does not support array-valued context field \"user.roles\"." + } + ] +} +``` + +The deprecated `missingContextFields` field remains available for older clients and reports only missing fields. For array membership, `CONTAINS` matches if the array includes the specified value, while `ANY_OF` matches if it includes at least one of the specified values. `NOT_CONTAINS` and `NOT_ANY_OF` match when those values are absent. `CONTAINS` retains substring matching only when the context value is a scalar string. `IS` requires an array with exactly one matching element; `IS_NOT` matches all other present arrays, including empty arrays. + ### `GET /features/enabled` -This endpoint is similar to `features/evaluated` but only includes flags that have been evaluated as `true`. +This endpoint is similar to `features/evaluated` but only includes flags that have been evaluated as `true`. It accepts the same `contextJson` parameter. ### `POST /features/events` @@ -294,6 +320,12 @@ Content-Type: application/json ``` {% endcode %} +## Array-valued attributes + +The `attributes` objects in user, company, and event requests, and equivalent bulk items, accept native JSON arrays. For example, send `"roles": ["admin", "editor"]`, not `"roles": "[\"admin\",\"editor\"]"`. Array updates replace the whole attribute; `[]` clears the list. Arrays are not flattened into numeric paths. + +See [array attributes](../../product-handbook/array-attributes.md) for supported operators, element normalization, storage limits, and reserved-key handling. + ## Responses The API returns a `200` status code for successful calls and a `400` status code for errors, including invalid request bodies. diff --git a/api/public-api/public-api-reference.md b/api/public-api/public-api-reference.md index 0be9732..938781f 100644 --- a/api/public-api/public-api-reference.md +++ b/api/public-api/public-api-reference.md @@ -67,6 +67,15 @@ | field | string |

Refers to a field of the context object.
Example: company.tier

| | values | string\[] | Array of values which will be compared with the value of the context field. Operators SET, NOT\_SET, IS\_TRUE, IS\_FALSE require 0 values, ANY\_OF and NOT\_ANY\_OF support multiple values. All the other operators require exactly one value. | | operator | enum(`IS`,`IS_NOT`,`ANY_OF`,`NOT_ANY_OF`,`CONTAINS`,`NOT_CONTAINS`","`GT`" ,`LT`,`AFTER`,`BEFORE`,`SET`,`NOT_SET`,`IS_TRUE`,`IS_FALSE`) | Operator for comparison of the context field with provided values. | + +For array-valued context fields: + +* `IS` matches if the array has exactly one element equal to the specified value; `IS_NOT` matches all other present arrays. +* `CONTAINS` matches if the array includes the specified value; `NOT_CONTAINS` matches if it does not. +* `ANY_OF` matches if the array includes at least one of the specified values; `NOT_ANY_OF` matches if it includes none. +* `SET` matches non-empty arrays; `NOT_SET` matches empty arrays. + +Array comparisons use normalized, case-sensitive whole values, not substrings. Numeric, date, and boolean operators do not support arrays. See [array attributes](../../product-handbook/array-attributes.md) for examples, normalization, and missing-field behavior. {% endtab %} {% tab title="Rollout Percentage" %} diff --git a/product-handbook/array-attributes.md b/product-handbook/array-attributes.md new file mode 100644 index 0000000..b2060da --- /dev/null +++ b/product-handbook/array-attributes.md @@ -0,0 +1,73 @@ +--- +description: Target users and companies by roles, permissions, tags, and other lists. +--- + +# Array attributes + +Custom attributes can contain arrays as well as scalar values. Use arrays for lists such as user roles, company entitlements, or event tags. Arrays are supported in user, company, event, and `other` evaluation-context attributes. + +## Sending arrays + +Send a JSON array, not a JSON-encoded string: + +```json +{ + "userId": "user-123", + "attributes": { + "roles": ["admin", "editor"] + } +} +``` + +Send this body to [`POST /user`](../api/public-api/README.md#post-user). The same attribute format works with `POST /company`, `POST /event`, bulk requests, and Segment traits/properties. + +For remote flag evaluation, send arrays using [`contextJson`](../api/public-api/README.md#get-featuresevaluated). If using an SDK, use a version that supports array-valued context transport; older clients may flatten arrays into indexed fields instead. + +## Targeting with arrays + +For `user.roles: ["admin", "editor"]`, create an access condition on **User attribute → roles**. Choose **contains** and enter `admin` to match users whose roles include `admin`. Choose **IS ANY OF** when you want to match any of several roles. + +| Operator | Array behavior | Example | +| --- | --- | --- | +| IS (`IS`) | Matches if the array has exactly one element and it equals the specified value. | `["admin"]` is `admin`; `["admin", "editor"]` is not. | +| IS NOT (`IS_NOT`) | Matches any other present array. | `["admin", "editor"]` and `[]` are not `admin`. | +| contains (`CONTAINS`) | Matches if the array includes the specified value. | `["admin", "editor"]` contains `admin`, but not `adm`. | +| does not contain (`NOT_CONTAINS`) | Matches if the array does not include the specified value. | `["admin", "editor"]` does not contain `owner`. | +| IS ANY OF (`ANY_OF`) | Matches if the array includes at least one of the specified values. | `["admin", "editor"]` matches `admin` or `owner`. | +| IS NOT ANY OF (`NOT_ANY_OF`) | Matches if the array includes none of the specified values. | `["admin", "editor"]` does not match a condition excluding `admin`. | +| IS SET (`SET`) | Matches a non-empty array. | `["admin"]` is set; `[]` is not. | +| IS NOT SET (`NOT_SET`) | Matches an empty array. | `[]` is not set. | + +Matching compares whole values, not substrings, and is case-sensitive. Array order and duplicate elements do not affect membership matching. `IS`, `IS_NOT`, `CONTAINS`, and `NOT_CONTAINS` take one comparison value. Unlike membership checks, `IS` also requires the array to have exactly one element: `["admin", "admin"] IS "admin"` is false. `ANY_OF` and `NOT_ANY_OF` take a list; `ANY_OF` requires only one overlap, not all configured values. + +For an empty array, `IS`, `CONTAINS`, and `ANY_OF` are false, while `IS_NOT`, `NOT_CONTAINS`, and `NOT_ANY_OF` are true. A missing field is different from an empty array: use `SET` or `NOT_SET` to test presence. Other operators on a missing evaluation-context field cause the targeting rule not to match. + +Scalar behavior is unchanged: `ANY_OF` still checks whether a single scalar value is among the configured values. For flag evaluation, `CONTAINS` on a scalar string remains a case-insensitive substring check, so `"SuperAdmin" CONTAINS "admin"` is true. In contrast, `["SuperAdmin"] CONTAINS "admin"` is false. + +{% hint style="warning" %} +Use `CONTAINS` or `ANY_OF` to test array membership. Use `IS` only when the array must contain exactly one matching element. Numeric, date, and boolean operators do not support array-valued attributes. Arrays also cannot be used as percentage-rollout identifiers; use a scalar such as `company.id`. + +During flag evaluation, encountering an unsupported array operation makes that entire targeting rule fail to match, even inside a negated condition. Other targeting rules can still match. Conditions skipped by boolean short-circuiting do not produce errors. +{% endhint %} + +These membership operators also work in company segments and event-attribute filters. + +## Values and nesting + +* String elements remain strings. Numbers and booleans are converted to strings: `[1, true]` matches configured values `1` and `true`. +* A `null` element becomes an empty string. `[null]` is still a non-empty array and therefore is set. +* Objects and nested arrays inside an array become compact JSON strings. For example, `[{"level":3}, ["a","b"]]` becomes `["{\"level\":3}", "[\"a\",\"b\"]"]`. They are opaque values: targeting inside their properties or array positions is not supported. +* Arrays remain single attributes. Target `user.roles`, not `user.roles.0`. +* In an evaluation request, a string such as `"[\"admin\"]"` remains a scalar string, not an array. Do not call `JSON.stringify()` on individual array attributes before sending them. + +Remote evaluation permits one object level within an attribute, for example `user.profile.roles`. Deeper object nesting outside arrays is rejected. + +## Storage and display + +Updating an array replaces the whole attribute; it does not append or merge elements. To clear a list, send `[]`. + +Ingest stores arrays natively after normalizing their elements. User/company attribute views and existing scalar-valued API responses may display them as compact JSON text, such as `["admin","editor"]`. This display format does not change their targeting behavior. + +Oversized stored arrays are replaced entirely with the scalar string `"[TRUNCATED]"`, rather than keeping a partial list. The default limits are 1,000 elements and 2,000 characters in the normalized serialized array; deployments may configure a different serialized-value limit. These storage limits are separate from remote evaluation's request-size limit. + +Ingest silently skips reserved keys `__proto__`, `constructor`, and `prototype`, including nested keys, dotted path segments, and variants containing null bytes. Other attributes are accepted normally. diff --git a/product-handbook/concepts/filter.md b/product-handbook/concepts/filter.md index 0504ef5..db39f46 100644 --- a/product-handbook/concepts/filter.md +++ b/product-handbook/concepts/filter.md @@ -19,6 +19,10 @@ Reflag supports the following filter types: This filter can be used to check company attributes against a set of predicates. The attributes include `First seen` and `Last seen`, which are maintained by Reflag. You can use any attribute name that your application sends to Reflag. +### Array-valued attributes + +User, company, event, and other-context attributes can contain arrays. `CONTAINS` matches if the array includes the specified value; `NOT_CONTAINS` matches if it does not. `ANY_OF` matches if the array includes at least one of the specified values; `NOT_ANY_OF` matches if it includes none. Use `SET`/`NOT_SET` for non-empty/empty arrays. Array membership compares whole values, is case-sensitive, and does not search for substrings inside elements. `IS` instead requires the array to have exactly one element equal to the specified value; `IS_NOT` matches all other present arrays. Numeric, date, and boolean operators remain scalar-only. See [array attributes](../array-attributes.md) for examples and normalization rules. + ### Company flag metrics This filter allows checking company-level flag metrics. These metrics include `Event count`, `First used`, `Last used`, and more. diff --git a/product-handbook/concepts/targeting-rules.md b/product-handbook/concepts/targeting-rules.md index bd8623e..a95ae13 100644 --- a/product-handbook/concepts/targeting-rules.md +++ b/product-handbook/concepts/targeting-rules.md @@ -18,7 +18,7 @@ The evaluation context refers simply to a collection of **key** — **value** pa * Any other [company attributes](company.md#attributes) that might be used by the filters in the rules, * A collection of "_**other**_" attributes that can be used by the feature access targeting rules. -The exact structure of the data will vary by the SDK in use. +The exact structure of the data will vary by the SDK in use. Custom attributes can also be [arrays](../array-attributes.md), such as `user.roles: ["admin", "editor"]`. `CONTAINS` matches if the array includes the specified value; `NOT_CONTAINS` matches if it does not. `ANY_OF` matches if the array includes at least one of the specified values; `NOT_ANY_OF` matches if it includes none of them. `IS` matches only if the array has exactly one element equal to the specified value; `IS_NOT` matches all other present arrays. Use `SET` and `NOT_SET` for empty/non-empty checks. For example, `user.roles CONTAINS ["admin"]` matches `["admin", "editor"]`. `user.roles ANY_OF ["admin", "owner"]` matches if either value is present. Array membership compares whole values and is case-sensitive; `CONTAINS` on a scalar string retains case-insensitive substring matching. ### Missing context fields @@ -26,6 +26,12 @@ During the evaluation of targeting rules against a context it might happen that Reflag reports these missing context fields using [feature events](feature-events.md). Reflag SDKs will also generate warnings in these cases making it easy to find these situations in your application. +### Unsupported array operations + +If an evaluated condition uses a scalar-only operator such as `GT`, `DATE_AFTER`, or `IS_TRUE` on an array, or uses an array for a percentage rollout, the entire targeting rule fails to match. Negating that condition does not make the rule match. Boolean short-circuiting is preserved: conditions that are not reached do not produce errors, and other targeting rules may still match. + +The [evaluation API](../../api/public-api/README.md#get-featuresevaluated) reports these cases in `evaluationErrors` with code `UNSUPPORTED_ARRAY_OPERATOR`. Missing context fields use `MISSING_CONTEXT_FIELD`. The deprecated `missingContextFields` field remains available for older clients. + ### Next steps * Learn about [filters](filter.md), diff --git a/product-handbook/feature-rollouts/feature-targeting-rules.md b/product-handbook/feature-rollouts/feature-targeting-rules.md index 2b3f8c4..4590ad7 100644 --- a/product-handbook/feature-rollouts/feature-targeting-rules.md +++ b/product-handbook/feature-rollouts/feature-targeting-rules.md @@ -72,6 +72,7 @@ Here are examples of access conditions: * Companies with Company IDs 1 and 2: `Company attribute: Company ID IS ANY OF [1,2]` * Give access to newly created companies: `Company attribute: createdAt LESS THAN [30] DAYS AGO` * Give access to users with the manager role at all companies: `User attribute: role IS [manager]` +* Give access to users whose `roles` array includes manager: `User attribute: roles IS ANY OF [manager]`. See [array attributes](../array-attributes.md) for supported operators and examples. * Give access to companies in the Pro plan segment: `Segment: In segment ['Pro']` * Give access to companies in the Beta users’ segment: `Segment: In segment ['Beta users']` * Give access to companies who already have access to the Huddle flag: `Flag access: Flag [Huddle] is enabled` From 6c864c592f40b573f533611f62c8fac45483524c Mon Sep 17 00:00:00 2001 From: Ron Cohen Date: Mon, 7 Sep 2026 15:31:57 +0200 Subject: [PATCH 2/4] Use plain language in array attribute docs --- api/public-api/README.md | 39 ++++++--- api/public-api/public-api-reference.md | 14 ++-- product-handbook/array-attributes.md | 83 +++++++++++-------- product-handbook/concepts/filter.md | 10 ++- product-handbook/concepts/targeting-rules.md | 14 +++- .../feature-targeting-rules.md | 2 +- 6 files changed, 105 insertions(+), 57 deletions(-) diff --git a/api/public-api/README.md b/api/public-api/README.md index e7497d1..9fb1a9c 100644 --- a/api/public-api/README.md +++ b/api/public-api/README.md @@ -104,7 +104,7 @@ This endpoint retrieves a list of flag values evaluated for a particular user or The endpoint is a `GET` request to ensure that the request can be completed without a `CORS Preflight` request to reduce latency. {% endhint %} -Send the context as JSON in a single URL-encoded `contextJson` query parameter. This preserves [array-valued attributes](../../product-handbook/array-attributes.md): +Send the context in one query parameter called `contextJson`. Use `JSON.stringify()` to turn the whole context into JSON text, then `URLSearchParams` to encode it for the URL. Arrays stay as arrays: ```javascript const context = { @@ -120,13 +120,21 @@ const response = await fetch(`https://front.reflag.com/features/evaluated?${quer const flags = await response.json(); ``` -`contextJson` accepts optional `user`, `company`, and `other` objects. User/company IDs must be strings or numbers, not arrays. Attribute values can be strings, numbers, booleans, null, arrays, or objects containing those leaf values. One object level within an attribute is allowed, such as `user.profile.roles`; deeper object nesting outside arrays is rejected. Objects and nested arrays inside arrays are opaque JSON-encoded elements, not individually addressable paths. +The context can include `user`, `company`, and `other` objects. None of these objects is required. User and company IDs are also optional; if provided, they must be strings or numbers, not arrays. -The decoded JSON parameter is limited to 16 KiB by default; HTTP servers or proxies may impose a smaller URL limit. Invalid JSON, invalid context shapes, duplicate `contextJson` parameters, or combining `contextJson` with `context`/`context.*` parameters returns a validation error. +Attributes can hold strings, numbers, true/false values, `null`, arrays, or objects. Remote evaluation allows one object level within an attribute: `user.profile.roles` is allowed, but `user.profile.settings.roles` is too deep. Objects and arrays inside an array are treated as single values. See [array attributes](../../product-handbook/array-attributes.md) for details. -The older dotted scalar format, such as `context.company.id=42&context.user.id=99`, remains supported. Do not encode arrays as `context.user.roles.0=admin`; use `contextJson` instead. +By default, the JSON text can be up to 16,384 bytes before URL encoding. Your HTTP server or proxy may have a lower URL limit. -#### Example using the older scalar format +The API returns a validation error if: + +* The JSON is not valid or the context does not follow the rules above. +* You send `contextJson` more than once. +* You send `contextJson` together with `context` or `context.*` parameters. + +The older query format still works for single values, such as `context.company.id=42&context.user.id=99`. For arrays, use `contextJson`; do not send separate items as `context.user.roles.0=admin`. + +#### Example using the older query format
GET https://front.reflag.com/features/evaluated?context.company.id=42&context.user.id=99&publishableKey=pub_prod_Cqx4DGo1lk3Lcct5NHLjWy
 
@@ -150,9 +158,14 @@ The older dotted scalar format, such as `context.company.id=42&context.user.id=9 Reflag utilizes attributes from the `company` endpoint to identify which features are enabled for specific companies. Ensure all `company` attributes referenced in the `context` are also provided through the `company` endpoint. {% endhint %} -#### Evaluation diagnostics +#### Evaluation errors + +Flag results may include `evaluationErrors`: -Flag results may include `evaluationErrors`. An encountered unsupported array operation makes its entire targeting rule fail to match; it does not cause an HTTP error or prevent other rules from matching. Missing fields use code `MISSING_CONTEXT_FIELD`; unsupported array operators use `UNSUPPORTED_ARRAY_OPERATOR`. Config evaluation can report its own errors in `config.evaluationErrors`. +* `MISSING_CONTEXT_FIELD`: a rule needs a field that was not provided. +* `UNSUPPORTED_ARRAY_OPERATOR`: a rule tried to use an operator that does not work with arrays. + +The affected rule does not match, but the API still returns a normal response and other rules can match. Errors while choosing a config value are listed in `config.evaluationErrors`. ```json { @@ -167,7 +180,9 @@ Flag results may include `evaluationErrors`. An encountered unsupported array op } ``` -The deprecated `missingContextFields` field remains available for older clients and reports only missing fields. For array membership, `CONTAINS` matches if the array includes the specified value, while `ANY_OF` matches if it includes at least one of the specified values. `NOT_CONTAINS` and `NOT_ANY_OF` match when those values are absent. `CONTAINS` retains substring matching only when the context value is a scalar string. `IS` requires an array with exactly one matching element; `IS_NOT` matches all other present arrays, including empty arrays. +Older clients can still use `missingContextFields`, which lists missing fields only. Use `evaluationErrors` for new integrations. + +See [array attributes](../../product-handbook/array-attributes.md#targeting-with-arrays) for the supported operators and examples. ### `GET /features/enabled` @@ -320,11 +335,13 @@ Content-Type: application/json ``` {% endcode %} -## Array-valued attributes +## Array attributes + +You can send arrays in the `attributes` of user, company, and event requests, including bulk requests. Send `"roles": ["admin", "editor"]`, not `"roles": "[\"admin\",\"editor\"]"`. -The `attributes` objects in user, company, and event requests, and equivalent bulk items, accept native JSON arrays. For example, send `"roles": ["admin", "editor"]`, not `"roles": "[\"admin\",\"editor\"]"`. Array updates replace the whole attribute; `[]` clears the list. Arrays are not flattened into numeric paths. +An update replaces the whole list; sending `[]` clears it. Each array stays one attribute, rather than becoming separate fields such as `roles.0`. -See [array attributes](../../product-handbook/array-attributes.md) for supported operators, element normalization, storage limits, and reserved-key handling. +See [array attributes](../../product-handbook/array-attributes.md) for matching rules, how Reflag reads each item, storage limits, and keys that Reflag ignores. ## Responses diff --git a/api/public-api/public-api-reference.md b/api/public-api/public-api-reference.md index 938781f..ff69a34 100644 --- a/api/public-api/public-api-reference.md +++ b/api/public-api/public-api-reference.md @@ -68,14 +68,16 @@ | values | string\[] | Array of values which will be compared with the value of the context field. Operators SET, NOT\_SET, IS\_TRUE, IS\_FALSE require 0 values, ANY\_OF and NOT\_ANY\_OF support multiple values. All the other operators require exactly one value. | | operator | enum(`IS`,`IS_NOT`,`ANY_OF`,`NOT_ANY_OF`,`CONTAINS`,`NOT_CONTAINS`","`GT`" ,`LT`,`AFTER`,`BEFORE`,`SET`,`NOT_SET`,`IS_TRUE`,`IS_FALSE`) | Operator for comparison of the context field with provided values. | -For array-valued context fields: +When the context field holds an array: -* `IS` matches if the array has exactly one element equal to the specified value; `IS_NOT` matches all other present arrays. -* `CONTAINS` matches if the array includes the specified value; `NOT_CONTAINS` matches if it does not. -* `ANY_OF` matches if the array includes at least one of the specified values; `NOT_ANY_OF` matches if it includes none. -* `SET` matches non-empty arrays; `NOT_SET` matches empty arrays. +* `IS` matches if the array has just one item equal to the value you chose. `IS_NOT` matches if it has more than one item, is empty, or its only item differs. +* `CONTAINS` matches if the array includes the value you chose. `NOT_CONTAINS` matches if it does not. +* `ANY_OF` matches if the array includes at least one of the values you chose. `NOT_ANY_OF` matches if it includes none. +* `SET` matches arrays with at least one item. `NOT_SET` matches empty arrays. -Array comparisons use normalized, case-sensitive whole values, not substrings. Numeric, date, and boolean operators do not support arrays. See [array attributes](../../product-handbook/array-attributes.md) for examples, normalization, and missing-field behavior. +These checks match whole values and treat uppercase and lowercase letters as different. For example, `["admin"]` does not contain `adm` or `Admin`. Numeric and date operators, plus `IS_TRUE` and `IS_FALSE`, do not work with arrays. + +See [array attributes](../../product-handbook/array-attributes.md) for examples, how Reflag reads array items, and what happens when a field is missing. {% endtab %} {% tab title="Rollout Percentage" %} diff --git a/product-handbook/array-attributes.md b/product-handbook/array-attributes.md index b2060da..bf270c2 100644 --- a/product-handbook/array-attributes.md +++ b/product-handbook/array-attributes.md @@ -4,11 +4,11 @@ description: Target users and companies by roles, permissions, tags, and other l # Array attributes -Custom attributes can contain arrays as well as scalar values. Use arrays for lists such as user roles, company entitlements, or event tags. Arrays are supported in user, company, event, and `other` evaluation-context attributes. +Use arrays for lists such as user roles, company permissions, or event tags. You can send arrays in user, company, event, and `other` context attributes. ## Sending arrays -Send a JSON array, not a JSON-encoded string: +Send a JSON array, not a string: ```json { @@ -19,55 +19,72 @@ Send a JSON array, not a JSON-encoded string: } ``` -Send this body to [`POST /user`](../api/public-api/README.md#post-user). The same attribute format works with `POST /company`, `POST /event`, bulk requests, and Segment traits/properties. +Send this body to [`POST /user`](../api/public-api/README.md#post-user). The same attribute format works with `POST /company`, `POST /event`, bulk requests, and Segment traits and properties. -For remote flag evaluation, send arrays using [`contextJson`](../api/public-api/README.md#get-featuresevaluated). If using an SDK, use a version that supports array-valued context transport; older clients may flatten arrays into indexed fields instead. +For remote flag checks, send arrays using [`contextJson`](../api/public-api/README.md#get-featuresevaluated). If you use an SDK, choose a version that can send arrays in context. Older versions may turn an array into separate fields such as `user.roles.0` and `user.roles.1`. ## Targeting with arrays -For `user.roles: ["admin", "editor"]`, create an access condition on **User attribute → roles**. Choose **contains** and enter `admin` to match users whose roles include `admin`. Choose **IS ANY OF** when you want to match any of several roles. +For `user.roles: ["admin", "editor"]`, add an access condition on **User attribute → roles**. Choose **contains** and enter `admin` to match users whose roles include `admin`. Choose **IS ANY OF** to check for several roles at once. -| Operator | Array behavior | Example | +| Operator | When it matches | Example | | --- | --- | --- | -| IS (`IS`) | Matches if the array has exactly one element and it equals the specified value. | `["admin"]` is `admin`; `["admin", "editor"]` is not. | -| IS NOT (`IS_NOT`) | Matches any other present array. | `["admin", "editor"]` and `[]` are not `admin`. | -| contains (`CONTAINS`) | Matches if the array includes the specified value. | `["admin", "editor"]` contains `admin`, but not `adm`. | -| does not contain (`NOT_CONTAINS`) | Matches if the array does not include the specified value. | `["admin", "editor"]` does not contain `owner`. | -| IS ANY OF (`ANY_OF`) | Matches if the array includes at least one of the specified values. | `["admin", "editor"]` matches `admin` or `owner`. | -| IS NOT ANY OF (`NOT_ANY_OF`) | Matches if the array includes none of the specified values. | `["admin", "editor"]` does not match a condition excluding `admin`. | -| IS SET (`SET`) | Matches a non-empty array. | `["admin"]` is set; `[]` is not. | -| IS NOT SET (`NOT_SET`) | Matches an empty array. | `[]` is not set. | +| IS (`IS`) | The array has just one item, equal to the value you chose. | `["admin"]` matches `IS admin`; `["admin", "editor"]` does not. | +| IS NOT (`IS_NOT`) | The array is empty, has more than one item, or its only item differs from the value you chose. | `["admin", "editor"]` and `[]` match `IS NOT admin`. | +| contains (`CONTAINS`) | The array includes the value you chose. | `["admin", "editor"]` contains `admin`, but not `adm`. | +| does not contain (`NOT_CONTAINS`) | The array does not include the value you chose. | `["admin", "editor"]` does not contain `owner`. | +| IS ANY OF (`ANY_OF`) | The array includes at least one of the values you chose. | `["admin", "editor"]` matches `IS ANY OF [admin, owner]`. | +| IS NOT ANY OF (`NOT_ANY_OF`) | The array includes none of the values you chose. | `["admin", "editor"]` matches `IS NOT ANY OF [owner, guest]`. | +| IS SET (`SET`) | The array has at least one item. | `["admin"]` is set; `[]` is not. | +| IS NOT SET (`NOT_SET`) | The array is empty. | `[]` is not set. | -Matching compares whole values, not substrings, and is case-sensitive. Array order and duplicate elements do not affect membership matching. `IS`, `IS_NOT`, `CONTAINS`, and `NOT_CONTAINS` take one comparison value. Unlike membership checks, `IS` also requires the array to have exactly one element: `["admin", "admin"] IS "admin"` is false. `ANY_OF` and `NOT_ANY_OF` take a list; `ANY_OF` requires only one overlap, not all configured values. +Array checks compare whole values and treat uppercase and lowercase letters as different. For example, `["admin"]` does not contain `adm` or `Admin`. -For an empty array, `IS`, `CONTAINS`, and `ANY_OF` are false, while `IS_NOT`, `NOT_CONTAINS`, and `NOT_ANY_OF` are true. A missing field is different from an empty array: use `SET` or `NOT_SET` to test presence. Other operators on a missing evaluation-context field cause the targeting rule not to match. +Use one comparison value with `IS`, `IS_NOT`, `CONTAINS`, or `NOT_CONTAINS`. Use a list with `ANY_OF` or `NOT_ANY_OF`. `ANY_OF` needs only one match, not every value in the list. -Scalar behavior is unchanged: `ANY_OF` still checks whether a single scalar value is among the configured values. For flag evaluation, `CONTAINS` on a scalar string remains a case-insensitive substring check, so `"SuperAdmin" CONTAINS "admin"` is true. In contrast, `["SuperAdmin"] CONTAINS "admin"` is false. +The order of items does not matter. Repeated items matter only for `IS` and `IS_NOT`: `["admin", "admin"]` fails `IS admin` because it has two items. -{% hint style="warning" %} -Use `CONTAINS` or `ANY_OF` to test array membership. Use `IS` only when the array must contain exactly one matching element. Numeric, date, and boolean operators do not support array-valued attributes. Arrays also cannot be used as percentage-rollout identifiers; use a scalar such as `company.id`. +For flag access, `CONTAINS` on a single string checks for part of the text and ignores letter case. For example, `"SuperAdmin" CONTAINS "admin"` is true, but `["SuperAdmin"] CONTAINS "admin"` is false. + +You can also use array checks in company segments and event filters. + +### Empty arrays and missing fields + +For `[]`, `IS`, `CONTAINS`, and `ANY_OF` are false. `IS_NOT`, `NOT_CONTAINS`, and `NOT_ANY_OF` are true. + +When checking flag access, a missing field is different from an empty array. `SET` is false for a missing field; `NOT_SET` is true. If Reflag checks a missing field with any other operator, that rule does not match. -During flag evaluation, encountering an unsupported array operation makes that entire targeting rule fail to match, even inside a negated condition. Other targeting rules can still match. Conditions skipped by boolean short-circuiting do not produce errors. +### Checks that do not support arrays + +Numeric and date operators, plus `IS_TRUE` and `IS_FALSE`, do not work with arrays. Use an ID, such as `company.id`, rather than an array for percentage rollouts. + +{% hint style="warning" %} +If Reflag checks an array with an unsupported operator in a flag access rule, the whole rule does not match. Adding `NOT` does not turn that error into a match. Other rules can still match, and conditions that Reflag skips do not cause errors. {% endhint %} -These membership operators also work in company segments and event-attribute filters. +## How Reflag reads array items + +* Strings stay as they are. Numbers and true/false values become strings: `[1, true]` matches the rule values `"1"` and `"true"`. +* `null` becomes an empty string. `[null]` still has one item, so it is `SET`. +* Objects and arrays inside an array become JSON strings without extra spaces. For example, `[{"level":3}, ["a","b"]]` becomes `["{\"level\":3}", "[\"a\",\"b\"]"]`. Reflag treats each of these strings as one value. You cannot target properties or items inside them. +* An array stays one attribute. Target `user.roles`, not `user.roles.0`. +* A string such as `"[\"admin\"]"` in an evaluation request stays a string, not an array. Do not call `JSON.stringify()` on each array attribute before sending it. + +Remote evaluation allows one object level within an attribute, such as `user.profile.roles`. A path such as `user.profile.settings.roles` is too deep. This limit does not apply to objects inside arrays, which are treated as whole values. -## Values and nesting +## Updating and viewing arrays -* String elements remain strings. Numbers and booleans are converted to strings: `[1, true]` matches configured values `1` and `true`. -* A `null` element becomes an empty string. `[null]` is still a non-empty array and therefore is set. -* Objects and nested arrays inside an array become compact JSON strings. For example, `[{"level":3}, ["a","b"]]` becomes `["{\"level\":3}", "[\"a\",\"b\"]"]`. They are opaque values: targeting inside their properties or array positions is not supported. -* Arrays remain single attributes. Target `user.roles`, not `user.roles.0`. -* In an evaluation request, a string such as `"[\"admin\"]"` remains a scalar string, not an array. Do not call `JSON.stringify()` on individual array attributes before sending them. +Sending an updated array replaces the whole list. It does not add to the old list or merge the two. To clear a list, send `[]`. -Remote evaluation permits one object level within an attribute, for example `user.profile.roles`. Deeper object nesting outside arrays is rejected. +Reflag stores the array after converting its items as described above. The app and some API responses show arrays as JSON text, such as `["admin","editor"]`. They still work as arrays in targeting rules. -## Storage and display +## Limits and ignored keys -Updating an array replaces the whole attribute; it does not append or merge elements. To clear a list, send `[]`. +The default storage limits for an array are: -Ingest stores arrays natively after normalizing their elements. User/company attribute views and existing scalar-valued API responses may display them as compact JSON text, such as `["admin","editor"]`. This display format does not change their targeting behavior. +* 1,000 items. +* 2,000 characters after converting the items and writing the array as JSON, including brackets and quotes. -Oversized stored arrays are replaced entirely with the scalar string `"[TRUNCATED]"`, rather than keeping a partial list. The default limits are 1,000 elements and 2,000 characters in the normalized serialized array; deployments may configure a different serialized-value limit. These storage limits are separate from remote evaluation's request-size limit. +If an array exceeds either limit, Reflag replaces the whole value with the string `"[TRUNCATED]"`. It does not keep part of the list. The server may use different limits. These storage limits are separate from the size limit for remote evaluation requests. -Ingest silently skips reserved keys `__proto__`, `constructor`, and `prototype`, including nested keys, dotted path segments, and variants containing null bytes. Other attributes are accepted normally. +Reflag ignores attribute keys named `__proto__`, `constructor`, or `prototype`. This also applies inside objects, in dotted names such as `profile.constructor`, and after removing null characters (`\u0000`) from names. Other attributes are still accepted. diff --git a/product-handbook/concepts/filter.md b/product-handbook/concepts/filter.md index db39f46..0e505fe 100644 --- a/product-handbook/concepts/filter.md +++ b/product-handbook/concepts/filter.md @@ -19,9 +19,15 @@ Reflag supports the following filter types: This filter can be used to check company attributes against a set of predicates. The attributes include `First seen` and `Last seen`, which are maintained by Reflag. You can use any attribute name that your application sends to Reflag. -### Array-valued attributes +### Array attributes -User, company, event, and other-context attributes can contain arrays. `CONTAINS` matches if the array includes the specified value; `NOT_CONTAINS` matches if it does not. `ANY_OF` matches if the array includes at least one of the specified values; `NOT_ANY_OF` matches if it includes none. Use `SET`/`NOT_SET` for non-empty/empty arrays. Array membership compares whole values, is case-sensitive, and does not search for substrings inside elements. `IS` instead requires the array to have exactly one element equal to the specified value; `IS_NOT` matches all other present arrays. Numeric, date, and boolean operators remain scalar-only. See [array attributes](../array-attributes.md) for examples and normalization rules. +Attributes can hold lists, such as `roles: ["admin", "editor"]`. + +* Use `CONTAINS` to check for one value, such as `admin`. +* Use `ANY_OF` to check for any of several values, such as `admin` or `owner`. +* Use `IS` when the list must have just one item, equal to the value you chose. + +Array checks match whole values and treat uppercase and lowercase letters as different. See [array attributes](../array-attributes.md) for all supported operators, examples, and limits. ### Company flag metrics diff --git a/product-handbook/concepts/targeting-rules.md b/product-handbook/concepts/targeting-rules.md index a95ae13..9e8c8a2 100644 --- a/product-handbook/concepts/targeting-rules.md +++ b/product-handbook/concepts/targeting-rules.md @@ -18,7 +18,9 @@ The evaluation context refers simply to a collection of **key** — **value** pa * Any other [company attributes](company.md#attributes) that might be used by the filters in the rules, * A collection of "_**other**_" attributes that can be used by the feature access targeting rules. -The exact structure of the data will vary by the SDK in use. Custom attributes can also be [arrays](../array-attributes.md), such as `user.roles: ["admin", "editor"]`. `CONTAINS` matches if the array includes the specified value; `NOT_CONTAINS` matches if it does not. `ANY_OF` matches if the array includes at least one of the specified values; `NOT_ANY_OF` matches if it includes none of them. `IS` matches only if the array has exactly one element equal to the specified value; `IS_NOT` matches all other present arrays. Use `SET` and `NOT_SET` for empty/non-empty checks. For example, `user.roles CONTAINS ["admin"]` matches `["admin", "editor"]`. `user.roles ANY_OF ["admin", "owner"]` matches if either value is present. Array membership compares whole values and is case-sensitive; `CONTAINS` on a scalar string retains case-insensitive substring matching. +The exact structure of the data will vary by the SDK in use. Custom attributes can also hold arrays, such as `user.roles: ["admin", "editor"]`. + +Use `CONTAINS` to check for a role such as `admin`, or `ANY_OF` to check for any of several roles. See [array attributes](../array-attributes.md#targeting-with-arrays) for examples and the full list of operators. ### Missing context fields @@ -26,11 +28,15 @@ During the evaluation of targeting rules against a context it might happen that Reflag reports these missing context fields using [feature events](feature-events.md). Reflag SDKs will also generate warnings in these cases making it easy to find these situations in your application. -### Unsupported array operations +### Checks that do not support arrays + +Some operators, such as `GT`, `DATE_AFTER`, and `IS_TRUE`, do not work with arrays. Arrays also cannot be used for percentage rollouts. + +If Reflag runs one of these checks on an array, the whole rule does not match. Adding `NOT` does not turn that error into a match. Other rules can still match. -If an evaluated condition uses a scalar-only operator such as `GT`, `DATE_AFTER`, or `IS_TRUE` on an array, or uses an array for a percentage rollout, the entire targeting rule fails to match. Negating that condition does not make the rule match. Boolean short-circuiting is preserved: conditions that are not reached do not produce errors, and other targeting rules may still match. +Reflag does not report errors for conditions it skips. For example, if the first condition in an `OR` group matches, Reflag does not check the remaining conditions in that group. -The [evaluation API](../../api/public-api/README.md#get-featuresevaluated) reports these cases in `evaluationErrors` with code `UNSUPPORTED_ARRAY_OPERATOR`. Missing context fields use `MISSING_CONTEXT_FIELD`. The deprecated `missingContextFields` field remains available for older clients. +The [evaluation API](../../api/public-api/README.md#evaluation-errors) reports these errors in `evaluationErrors`, with code `UNSUPPORTED_ARRAY_OPERATOR`. Missing fields use `MISSING_CONTEXT_FIELD`. The older `missingContextFields` field is still available but lists missing fields only. ### Next steps diff --git a/product-handbook/feature-rollouts/feature-targeting-rules.md b/product-handbook/feature-rollouts/feature-targeting-rules.md index 4590ad7..e077072 100644 --- a/product-handbook/feature-rollouts/feature-targeting-rules.md +++ b/product-handbook/feature-rollouts/feature-targeting-rules.md @@ -72,7 +72,7 @@ Here are examples of access conditions: * Companies with Company IDs 1 and 2: `Company attribute: Company ID IS ANY OF [1,2]` * Give access to newly created companies: `Company attribute: createdAt LESS THAN [30] DAYS AGO` * Give access to users with the manager role at all companies: `User attribute: role IS [manager]` -* Give access to users whose `roles` array includes manager: `User attribute: roles IS ANY OF [manager]`. See [array attributes](../array-attributes.md) for supported operators and examples. +* Give access to users whose `roles` list includes `manager`: `User attribute: roles contains [manager]`. See [array attributes](../array-attributes.md) for more examples. * Give access to companies in the Pro plan segment: `Segment: In segment ['Pro']` * Give access to companies in the Beta users’ segment: `Segment: In segment ['Beta users']` * Give access to companies who already have access to the Huddle flag: `Flag access: Flag [Huddle] is enabled` From dbb0d1714dd30500d72a9b2ee7a3745be333cd58 Mon Sep 17 00:00:00 2001 From: Ron Cohen Date: Tue, 8 Sep 2026 19:51:57 +0200 Subject: [PATCH 3/4] Consolidate array operator documentation --- SUMMARY.md | 1 - api/public-api/README.md | 6 +- api/public-api/public-api-reference.md | 2 +- product-handbook/array-attributes.md | 90 ------------------- product-handbook/concepts/filter.md | 2 +- product-handbook/concepts/targeting-rules.md | 2 +- product-handbook/creating-segments.md | 21 +++++ .../feature-targeting-rules.md | 2 +- 8 files changed, 28 insertions(+), 98 deletions(-) delete mode 100644 product-handbook/array-attributes.md diff --git a/SUMMARY.md b/SUMMARY.md index 6739bce..5749683 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -57,7 +57,6 @@ * [Access rules](product-handbook/feature-rollouts/feature-targeting-rules.md) * [Flag clean-up and archival](product-handbook/feature-clean-up-and-archival-beta/README.md) * [AI code clean-up](product-handbook/feature-clean-up-and-archival-beta/ai-code-clean-up-beta.md) -* [Array attributes](product-handbook/array-attributes.md) * [Remote config](product-handbook/remote-config.md) * [Type safety](product-handbook/type-safety.md) * [Team permissions](product-handbook/team-permissions.md) diff --git a/api/public-api/README.md b/api/public-api/README.md index 9fb1a9c..8b4e7f4 100644 --- a/api/public-api/README.md +++ b/api/public-api/README.md @@ -122,7 +122,7 @@ const flags = await response.json(); The context can include `user`, `company`, and `other` objects. None of these objects is required. User and company IDs are also optional; if provided, they must be strings or numbers, not arrays. -Attributes can hold strings, numbers, true/false values, `null`, arrays, or objects. Remote evaluation allows one object level within an attribute: `user.profile.roles` is allowed, but `user.profile.settings.roles` is too deep. Objects and arrays inside an array are treated as single values. See [array attributes](../../product-handbook/array-attributes.md) for details. +Attributes can hold strings, numbers, true/false values, `null`, arrays, or objects. Remote evaluation allows one object level within an attribute: `user.profile.roles` is allowed, but `user.profile.settings.roles` is too deep. Objects and arrays inside an array are treated as single values. By default, the JSON text can be up to 16,384 bytes before URL encoding. Your HTTP server or proxy may have a lower URL limit. @@ -182,7 +182,7 @@ The affected rule does not match, but the API still returns a normal response an Older clients can still use `missingContextFields`, which lists missing fields only. Use `evaluationErrors` for new integrations. -See [array attributes](../../product-handbook/array-attributes.md#targeting-with-arrays) for the supported operators and examples. +See [array attribute operators](../../product-handbook/creating-segments.md#array-attributes) for supported operators and examples. ### `GET /features/enabled` @@ -341,7 +341,7 @@ You can send arrays in the `attributes` of user, company, and event requests, in An update replaces the whole list; sending `[]` clears it. Each array stays one attribute, rather than becoming separate fields such as `roles.0`. -See [array attributes](../../product-handbook/array-attributes.md) for matching rules, how Reflag reads each item, storage limits, and keys that Reflag ignores. +See [array attribute operators](../../product-handbook/creating-segments.md#array-attributes) for matching rules and examples. ## Responses diff --git a/api/public-api/public-api-reference.md b/api/public-api/public-api-reference.md index ff69a34..b672204 100644 --- a/api/public-api/public-api-reference.md +++ b/api/public-api/public-api-reference.md @@ -77,7 +77,7 @@ When the context field holds an array: These checks match whole values and treat uppercase and lowercase letters as different. For example, `["admin"]` does not contain `adm` or `Admin`. Numeric and date operators, plus `IS_TRUE` and `IS_FALSE`, do not work with arrays. -See [array attributes](../../product-handbook/array-attributes.md) for examples, how Reflag reads array items, and what happens when a field is missing. +See [array attribute operators](../../product-handbook/creating-segments.md#array-attributes) for examples. {% endtab %} {% tab title="Rollout Percentage" %} diff --git a/product-handbook/array-attributes.md b/product-handbook/array-attributes.md deleted file mode 100644 index bf270c2..0000000 --- a/product-handbook/array-attributes.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -description: Target users and companies by roles, permissions, tags, and other lists. ---- - -# Array attributes - -Use arrays for lists such as user roles, company permissions, or event tags. You can send arrays in user, company, event, and `other` context attributes. - -## Sending arrays - -Send a JSON array, not a string: - -```json -{ - "userId": "user-123", - "attributes": { - "roles": ["admin", "editor"] - } -} -``` - -Send this body to [`POST /user`](../api/public-api/README.md#post-user). The same attribute format works with `POST /company`, `POST /event`, bulk requests, and Segment traits and properties. - -For remote flag checks, send arrays using [`contextJson`](../api/public-api/README.md#get-featuresevaluated). If you use an SDK, choose a version that can send arrays in context. Older versions may turn an array into separate fields such as `user.roles.0` and `user.roles.1`. - -## Targeting with arrays - -For `user.roles: ["admin", "editor"]`, add an access condition on **User attribute → roles**. Choose **contains** and enter `admin` to match users whose roles include `admin`. Choose **IS ANY OF** to check for several roles at once. - -| Operator | When it matches | Example | -| --- | --- | --- | -| IS (`IS`) | The array has just one item, equal to the value you chose. | `["admin"]` matches `IS admin`; `["admin", "editor"]` does not. | -| IS NOT (`IS_NOT`) | The array is empty, has more than one item, or its only item differs from the value you chose. | `["admin", "editor"]` and `[]` match `IS NOT admin`. | -| contains (`CONTAINS`) | The array includes the value you chose. | `["admin", "editor"]` contains `admin`, but not `adm`. | -| does not contain (`NOT_CONTAINS`) | The array does not include the value you chose. | `["admin", "editor"]` does not contain `owner`. | -| IS ANY OF (`ANY_OF`) | The array includes at least one of the values you chose. | `["admin", "editor"]` matches `IS ANY OF [admin, owner]`. | -| IS NOT ANY OF (`NOT_ANY_OF`) | The array includes none of the values you chose. | `["admin", "editor"]` matches `IS NOT ANY OF [owner, guest]`. | -| IS SET (`SET`) | The array has at least one item. | `["admin"]` is set; `[]` is not. | -| IS NOT SET (`NOT_SET`) | The array is empty. | `[]` is not set. | - -Array checks compare whole values and treat uppercase and lowercase letters as different. For example, `["admin"]` does not contain `adm` or `Admin`. - -Use one comparison value with `IS`, `IS_NOT`, `CONTAINS`, or `NOT_CONTAINS`. Use a list with `ANY_OF` or `NOT_ANY_OF`. `ANY_OF` needs only one match, not every value in the list. - -The order of items does not matter. Repeated items matter only for `IS` and `IS_NOT`: `["admin", "admin"]` fails `IS admin` because it has two items. - -For flag access, `CONTAINS` on a single string checks for part of the text and ignores letter case. For example, `"SuperAdmin" CONTAINS "admin"` is true, but `["SuperAdmin"] CONTAINS "admin"` is false. - -You can also use array checks in company segments and event filters. - -### Empty arrays and missing fields - -For `[]`, `IS`, `CONTAINS`, and `ANY_OF` are false. `IS_NOT`, `NOT_CONTAINS`, and `NOT_ANY_OF` are true. - -When checking flag access, a missing field is different from an empty array. `SET` is false for a missing field; `NOT_SET` is true. If Reflag checks a missing field with any other operator, that rule does not match. - -### Checks that do not support arrays - -Numeric and date operators, plus `IS_TRUE` and `IS_FALSE`, do not work with arrays. Use an ID, such as `company.id`, rather than an array for percentage rollouts. - -{% hint style="warning" %} -If Reflag checks an array with an unsupported operator in a flag access rule, the whole rule does not match. Adding `NOT` does not turn that error into a match. Other rules can still match, and conditions that Reflag skips do not cause errors. -{% endhint %} - -## How Reflag reads array items - -* Strings stay as they are. Numbers and true/false values become strings: `[1, true]` matches the rule values `"1"` and `"true"`. -* `null` becomes an empty string. `[null]` still has one item, so it is `SET`. -* Objects and arrays inside an array become JSON strings without extra spaces. For example, `[{"level":3}, ["a","b"]]` becomes `["{\"level\":3}", "[\"a\",\"b\"]"]`. Reflag treats each of these strings as one value. You cannot target properties or items inside them. -* An array stays one attribute. Target `user.roles`, not `user.roles.0`. -* A string such as `"[\"admin\"]"` in an evaluation request stays a string, not an array. Do not call `JSON.stringify()` on each array attribute before sending it. - -Remote evaluation allows one object level within an attribute, such as `user.profile.roles`. A path such as `user.profile.settings.roles` is too deep. This limit does not apply to objects inside arrays, which are treated as whole values. - -## Updating and viewing arrays - -Sending an updated array replaces the whole list. It does not add to the old list or merge the two. To clear a list, send `[]`. - -Reflag stores the array after converting its items as described above. The app and some API responses show arrays as JSON text, such as `["admin","editor"]`. They still work as arrays in targeting rules. - -## Limits and ignored keys - -The default storage limits for an array are: - -* 1,000 items. -* 2,000 characters after converting the items and writing the array as JSON, including brackets and quotes. - -If an array exceeds either limit, Reflag replaces the whole value with the string `"[TRUNCATED]"`. It does not keep part of the list. The server may use different limits. These storage limits are separate from the size limit for remote evaluation requests. - -Reflag ignores attribute keys named `__proto__`, `constructor`, or `prototype`. This also applies inside objects, in dotted names such as `profile.constructor`, and after removing null characters (`\u0000`) from names. Other attributes are still accepted. diff --git a/product-handbook/concepts/filter.md b/product-handbook/concepts/filter.md index 0e505fe..20453eb 100644 --- a/product-handbook/concepts/filter.md +++ b/product-handbook/concepts/filter.md @@ -27,7 +27,7 @@ Attributes can hold lists, such as `roles: ["admin", "editor"]`. * Use `ANY_OF` to check for any of several values, such as `admin` or `owner`. * Use `IS` when the list must have just one item, equal to the value you chose. -Array checks match whole values and treat uppercase and lowercase letters as different. See [array attributes](../array-attributes.md) for all supported operators, examples, and limits. +Array checks match whole values and treat uppercase and lowercase letters as different. See [array attribute operators](../creating-segments.md#array-attributes) for all supported operators and examples. ### Company flag metrics diff --git a/product-handbook/concepts/targeting-rules.md b/product-handbook/concepts/targeting-rules.md index 9e8c8a2..e2615b7 100644 --- a/product-handbook/concepts/targeting-rules.md +++ b/product-handbook/concepts/targeting-rules.md @@ -20,7 +20,7 @@ The evaluation context refers simply to a collection of **key** — **value** pa The exact structure of the data will vary by the SDK in use. Custom attributes can also hold arrays, such as `user.roles: ["admin", "editor"]`. -Use `CONTAINS` to check for a role such as `admin`, or `ANY_OF` to check for any of several roles. See [array attributes](../array-attributes.md#targeting-with-arrays) for examples and the full list of operators. +Use `CONTAINS` to check for a role such as `admin`, or `ANY_OF` to check for any of several roles. See [array attribute operators](../creating-segments.md#array-attributes) for examples and the full list of operators. ### Missing context fields diff --git a/product-handbook/creating-segments.md b/product-handbook/creating-segments.md index 4967004..bc9ffe6 100644 --- a/product-handbook/creating-segments.md +++ b/product-handbook/creating-segments.md @@ -92,6 +92,27 @@ Operators depend on the condition type: * `In segment` * `Not in segment` +These attribute operators are also available when you create flag access rules. + +#### Array attributes + +Attributes can hold arrays such as `roles: ["admin", "editor"]`. Array operators compare whole items and are case-sensitive: `admin` does not match `Admin` or part of a value such as `adm`. + +| Operator | Matches an array when | +| --- | --- | +| `Is` | It has exactly one item, equal to the selected value. | +| `Is not` | It is empty, has more than one item, or its only item differs from the selected value. | +| `Contains` | It includes the selected value. | +| `Does not contain` | It does not include the selected value. | +| `Is any of` | It includes at least one selected value. | +| `Is not any of` | It includes none of the selected values. | +| `Has any value` | It has at least one item. | +| `Has no value` | It is empty. | + +For example, `roles: ["admin", "editor"]` matches `Contains admin` and `Is any of [admin, owner]`, but it does not match `Is admin`. + +Number, date, and boolean operators do not support arrays, and arrays cannot be used for percentage rollouts. If an access rule applies an unsupported operator to an array, that rule does not match. + ## Save and reuse the segment After you save a segment, you can reuse it in flag [access rules](feature-rollouts/feature-targeting-rules.md) and rollout workflows. diff --git a/product-handbook/feature-rollouts/feature-targeting-rules.md b/product-handbook/feature-rollouts/feature-targeting-rules.md index e077072..f358e9a 100644 --- a/product-handbook/feature-rollouts/feature-targeting-rules.md +++ b/product-handbook/feature-rollouts/feature-targeting-rules.md @@ -72,7 +72,7 @@ Here are examples of access conditions: * Companies with Company IDs 1 and 2: `Company attribute: Company ID IS ANY OF [1,2]` * Give access to newly created companies: `Company attribute: createdAt LESS THAN [30] DAYS AGO` * Give access to users with the manager role at all companies: `User attribute: role IS [manager]` -* Give access to users whose `roles` list includes `manager`: `User attribute: roles contains [manager]`. See [array attributes](../array-attributes.md) for more examples. +* Give access to users whose `roles` list includes `manager`: `User attribute: roles contains [manager]`. See [array attribute operators](../creating-segments.md#array-attributes) for more examples. * Give access to companies in the Pro plan segment: `Segment: In segment ['Pro']` * Give access to companies in the Beta users’ segment: `Segment: In segment ['Beta users']` * Give access to companies who already have access to the Huddle flag: `Flag access: Flag [Huddle] is enabled` From a891a184928b0748313be701266e6877cfe7a6d8 Mon Sep 17 00:00:00 2001 From: Ron Cohen Date: Tue, 8 Sep 2026 19:56:55 +0200 Subject: [PATCH 4/4] Clarify nested evaluation context --- api/public-api/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/public-api/README.md b/api/public-api/README.md index 8b4e7f4..26fabf3 100644 --- a/api/public-api/README.md +++ b/api/public-api/README.md @@ -122,7 +122,7 @@ const flags = await response.json(); The context can include `user`, `company`, and `other` objects. None of these objects is required. User and company IDs are also optional; if provided, they must be strings or numbers, not arrays. -Attributes can hold strings, numbers, true/false values, `null`, arrays, or objects. Remote evaluation allows one object level within an attribute: `user.profile.roles` is allowed, but `user.profile.settings.roles` is too deep. Objects and arrays inside an array are treated as single values. +Context attributes can hold strings, numbers, true/false values, `null`, or arrays. You can group attributes in one nested object—for example, `user.profile.roles`, targeted with the same dotted path. Deeper object nesting is rejected. By default, the JSON text can be up to 16,384 bytes before URL encoding. Your HTTP server or proxy may have a lower URL limit.