From db56f170cbb8ed5958cef7a7f8ccbadf70cbc7f2 Mon Sep 17 00:00:00 2001 From: Madhu Balakrishna Date: Fri, 16 Oct 2026 14:24:40 +0530 Subject: [PATCH 1/4] [Flagship] Add structured context support and evaluation API documentation --- src/content/docs/flagship/binding/index.mdx | 3 +- src/content/docs/flagship/binding/types.mdx | 41 +++++++++++---- src/content/docs/flagship/concepts.mdx | 16 ++++-- src/content/docs/flagship/index.mdx | 2 +- .../docs/flagship/reference/api-reference.mdx | 4 +- .../flagship/reference/evaluation-api.mdx | 51 +++++++++++++++++++ .../flagship/reference/evaluation-reasons.mdx | 14 ++++- .../docs/flagship/reference/limits.mdx | 21 ++++++-- .../flagship/reference/wrangler-commands.mdx | 5 +- .../docs/flagship/sdk/client-provider.mdx | 6 +++ src/content/docs/flagship/sdk/go.mdx | 2 +- src/content/docs/flagship/sdk/index.mdx | 16 ++++++ src/content/docs/flagship/sdk/python.mdx | 2 +- .../docs/flagship/sdk/server-provider.mdx | 4 +- src/content/docs/flagship/targeting/index.mdx | 24 ++++++++- .../docs/flagship/targeting/operators.mdx | 50 +++++++++++------- .../targeting/percentage-rollouts.mdx | 18 +++++-- 17 files changed, 229 insertions(+), 50 deletions(-) create mode 100644 src/content/docs/flagship/reference/evaluation-api.mdx diff --git a/src/content/docs/flagship/binding/index.mdx b/src/content/docs/flagship/binding/index.mdx index c1e31a02cb9..47f56a678f6 100644 --- a/src/content/docs/flagship/binding/index.mdx +++ b/src/content/docs/flagship/binding/index.mdx @@ -41,7 +41,8 @@ The binding provides type-safe methods for evaluating feature flags. If an evalu export default { async fetch(request: Request, env: Env): Promise { const enabled = await env.FLAGS.getBooleanValue("new-feature", false, { - userId: "user-42", + profile: { plan: "enterprise" }, + tags: ["beta"], }); return new Response(enabled ? "Feature on" : "Feature off"); }, diff --git a/src/content/docs/flagship/binding/types.mdx b/src/content/docs/flagship/binding/types.mdx index 64ba486196f..30029837099 100644 --- a/src/content/docs/flagship/binding/types.mdx +++ b/src/content/docs/flagship/binding/types.mdx @@ -8,7 +8,7 @@ products: - flagship --- -The Flagship binding uses the following TypeScript types. These are available from the `@cloudflare/workers-types` package after running `npx wrangler types`. +The Flagship binding runtime uses the following TypeScript shapes. The published Workers type declarations do not yet include every runtime value. ## `Flagship` @@ -24,12 +24,31 @@ Refer to the [methods reference](/flagship/binding/methods/) for the full list o ## `FlagshipEvaluationContext` -A record of attribute names to values passed for [targeting rules](/flagship/targeting/). Use this to provide user attributes such as user ID, country, or plan type. +A record of attributes passed to [targeting rules](/flagship/targeting/). Values can recursively contain arrays and objects. ```ts -type FlagshipEvaluationContext = Record; +type FlagshipEvaluationContextValue = + | string + | number + | boolean + | null + | FlagshipEvaluationContextValue[] + | { [key: string]: FlagshipEvaluationContextValue }; + +type FlagshipEvaluationContext = Record< + string, + FlagshipEvaluationContextValue +>; ``` +Numbers must be finite. The binding rejects `NaN`, `Infinity`, unsupported objects, and cyclic values. + +The runtime also accepts valid JavaScript `Date` values. The binding normalizes each date to an ISO 8601 string. Invalid dates produce an `INVALID_CONTEXT` error. + +The public declarations from `wrangler types` and `@cloudflare/workers-types` still define context as `Record`. Therefore, TypeScript can reject structured context that the runtime accepts. [workerd pull request #7462](https://github.com/cloudflare/workerd/pull/7462) tracks the recursive public declarations and remains open. + +Until those declarations are published, TypeScript requires an explicit cast for structured context. This declaration mismatch does not change runtime support. Serialize `Date` values yourself when you need declaration-compatible code. + ## `FlagshipEvaluationDetails` Returned by the `*Details` methods. Contains the evaluated value and metadata about how Flagship resolved the flag. @@ -41,15 +60,17 @@ interface FlagshipEvaluationDetails { variant?: string; reason?: string; errorCode?: string; + errorMessage?: string; } ``` -| Property | Type | Description | -| ----------- | -------- | -------------------------------------------------------------------------------------- | -| `flagKey` | `string` | The key of the evaluated flag. | -| `value` | `T` | The resolved flag value. | -| `variant` | `string` | The name of the matched variant, if any. | -| `reason` | `string` | Why the flag resolved to this value (for example, `"TARGETING_MATCH"` or `"DEFAULT"`). | -| `errorCode` | `string` | An error code if evaluation failed (for example, `"TYPE_MISMATCH"` or `"GENERAL"`). | +| Property | Type | Description | +| -------------- | -------- | -------------------------------------------------------------------------------------- | +| `flagKey` | `string` | The key of the evaluated flag. | +| `value` | `T` | The resolved flag value. | +| `variant` | `string` | The name of the matched variant, if any. | +| `reason` | `string` | Why the flag resolved to this value (for example, `"TARGETING_MATCH"` or `"DEFAULT"`). | +| `errorCode` | `string` | An error code if evaluation failed (for example, `"TYPE_MISMATCH"` or `"GENERAL"`). | +| `errorMessage` | `string` | A description of the evaluation error, if any. | Refer to [evaluation reasons and error codes](/flagship/reference/evaluation-reasons/) for the full list of possible values. diff --git a/src/content/docs/flagship/concepts.mdx b/src/content/docs/flagship/concepts.mdx index 1ac7a49c3d5..2a88ca5cc01 100644 --- a/src/content/docs/flagship/concepts.mdx +++ b/src/content/docs/flagship/concepts.mdx @@ -8,6 +8,8 @@ products: - flagship --- +import { TypeScriptExample } from "~/components"; + Flagship organizes feature flags into apps. You define flags with variants and targeting rules, then evaluate them within Cloudflare's global network. ## Overview @@ -65,21 +67,29 @@ Refer to [Targeting rules](/flagship/targeting/) and [Operators](/flagship/targe ## Evaluation context -The evaluation context is a set of key-value attributes that describe the current user or request (for example, `userId`, `country`, `plan`). +The evaluation context describes the current user or request. It is an object whose top-level keys are attribute names. + +Each value can be a string, finite number, boolean, `null`, array, or nested object. Arrays and objects can recursively contain the same value types. You pass the context as the third argument to evaluation methods on the binding: + + ```ts const value = await env.FLAGS.getBooleanValue("new-checkout", false, { - userId: "user-42", - country: "US", + profile: { plan: "enterprise" }, + tags: ["beta"], }); ``` + + When using the [OpenFeature SDK](/flagship/sdk/), you pass context through the OpenFeature evaluation context object. Flagship uses context attributes to match targeting rules and to determine percentage rollout bucketing. A consistent context (for example, the same `userId`) produces the same rollout result on every evaluation. +Evaluation context is input to an evaluation. It is separate from [JSON flag variants](#variants), which are values returned by an evaluation. + Avoid sending sensitive data in evaluation context. Only include attributes needed by targeting rules or rollout bucketing. ## Flag propagation diff --git a/src/content/docs/flagship/index.mdx b/src/content/docs/flagship/index.mdx index 7c7177def8c..c1bad660777 100644 --- a/src/content/docs/flagship/index.mdx +++ b/src/content/docs/flagship/index.mdx @@ -47,7 +47,7 @@ Use the official OpenFeature SDKs to evaluate flags from Workers, Node.js, brows -Serve different flag values based on user attributes. Rules support 11 comparison operators, logical AND/OR grouping, and sequential evaluation. +Serve different flag values based on user attributes. Rules support 13 comparison operators, logical AND/OR grouping, and sequential evaluation. diff --git a/src/content/docs/flagship/reference/api-reference.mdx b/src/content/docs/flagship/reference/api-reference.mdx index f4697f2e894..bc376de9795 100644 --- a/src/content/docs/flagship/reference/api-reference.mdx +++ b/src/content/docs/flagship/reference/api-reference.mdx @@ -1,10 +1,10 @@ --- pcx_content_type: navigation title: API reference -description: API endpoints for managing Flagship apps, flags, and targeting rules. +description: API endpoints for managing and evaluating Flagship apps, flags, and targeting rules. products: - flagship external_link: /api/resources/flagship/ sidebar: - order: 3 + order: 4 --- diff --git a/src/content/docs/flagship/reference/evaluation-api.mdx b/src/content/docs/flagship/reference/evaluation-api.mdx new file mode 100644 index 00000000000..d3404bd6ec4 --- /dev/null +++ b/src/content/docs/flagship/reference/evaluation-api.mdx @@ -0,0 +1,51 @@ +--- +pcx_content_type: reference +title: Evaluation API +description: Evaluate flags with scalar query or structured JSON context. +sidebar: + order: 3 +products: + - flagship +--- + +import { APIRequest } from "~/components"; + +Use the evaluation API when you cannot use the [Workers binding](/flagship/binding/). The API accepts query parameters or a JSON request body. + +## Send scalar query context + +The [`GET` endpoint](/api/resources/flagship/subresources/apps/subresources/evaluate/methods/get/) accepts context attributes as query parameters. Use it for scalar context values. + +Flagship coerces unambiguous number and boolean query values. For example, `42` becomes a number and `true` becomes a boolean. Other values remain strings. + +JSON-looking query values remain strings. A value such as `{"plan":"enterprise"}` does not create a nested object. + +## Send structured JSON context + +Use `POST` to send nested objects, arrays, or `null`. The following request evaluates `new-checkout` with structured context: + + + +The `POST` response contains the same fields as `GET`: + +```json +{ + "flagKey": "new-checkout", + "value": true, + "variant": "on", + "reason": "TARGETING_MATCH" +} +``` + +Invalid context returns a `400` response. Refer to [Limits](/flagship/reference/limits/) and [evaluation error codes](/flagship/reference/evaluation-reasons/#error-codes) for details. diff --git a/src/content/docs/flagship/reference/evaluation-reasons.mdx b/src/content/docs/flagship/reference/evaluation-reasons.mdx index 6de2c895263..262ec4068e8 100644 --- a/src/content/docs/flagship/reference/evaluation-reasons.mdx +++ b/src/content/docs/flagship/reference/evaluation-reasons.mdx @@ -16,6 +16,7 @@ When you evaluate a flag using the binding's `*Details` methods or the OpenFeatu | Reason | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `STATIC` | The enabled flag has no targeting rules. Its default variant was returned. | | `TARGETING_MATCH` | A targeting rule's conditions matched the evaluation context, and the rule's variant was returned. | | `SPLIT` | A targeting rule with a percentage rollout matched. The user fell within the rollout percentage and received the rule's variant. | | `STATIC` | The flag is enabled and has no targeting rules, so its default variant was returned. | @@ -32,10 +33,16 @@ When an evaluation error occurs, the method returns the default value you provid | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TYPE_MISMATCH` | The flag's variant type does not match the requested type. For example, calling `getBooleanValue` on a flag whose variant is a string. The default value is returned. | | `FLAG_NOT_FOUND` | The specified flag key does not exist in the app. The default value is returned. | -| `INVALID_CONTEXT` | The evaluation context contains unsupported values, such as objects or arrays in HTTP evaluation. The default value is returned. | +| `INVALID_CONTEXT` | The context has an invalid shape or unsupported value, contains a non-finite number or cycle, or exceeds a limit. | | `PARSE_ERROR` | The SDK received an invalid evaluation response. The default value is returned. | | `GENERAL` | An unexpected error occurred during evaluation, such as a timeout or network failure. The default value is returned. | +## Invalid context behavior + +Binding `*Details()` methods return the supplied default value for invalid context. They set `reason` to `ERROR`, `errorCode` to `INVALID_CONTEXT`, and include an `errorMessage`. + +The direct data-plane HTTP `POST` endpoint returns a `400` response instead. Refer to [Evaluation API](/flagship/reference/evaluation-api/) for a request example. + ## Example The following example inspects evaluation details returned by `getBooleanDetails`: @@ -48,6 +55,9 @@ const details = await env.FLAGS.getBooleanDetails("my-feature", false, { }); switch (details.reason) { + case "STATIC": + console.log("No targeting rules, using default variant"); + break; case "TARGETING_MATCH": console.log(`Matched targeting rule, variant: ${details.variant}`); break; @@ -69,7 +79,7 @@ switch (details.reason) { } if (details.errorCode) { - console.error(`Evaluation error: ${details.errorCode}`); + console.error(`Evaluation error: ${details.errorCode}`, details.errorMessage); } ``` diff --git a/src/content/docs/flagship/reference/limits.mdx b/src/content/docs/flagship/reference/limits.mdx index 93fa6cb575e..d2eb500a524 100644 --- a/src/content/docs/flagship/reference/limits.mdx +++ b/src/content/docs/flagship/reference/limits.mdx @@ -1,7 +1,7 @@ --- pcx_content_type: reference title: Limits -description: Platform limits for Flagship, including maximum apps per account, flags per app, condition nesting depth, and configuration size. +description: Platform and evaluation context limits for Flagship apps, flags, rules, and requests. sidebar: order: 1 products: @@ -20,7 +20,7 @@ Flagship enforces the following limits. | Condition attribute names | 64 chars | | Condition string values | 256 chars | | Variant value size | 10 KB | -| Condition nesting depth | 5 levels | +| Condition grouping depth | 5 levels | | Flag description | 512 chars | | Flag configuration size per app | 25 MB | @@ -28,9 +28,24 @@ Flagship enforces the following limits. The apps-per-account and flags-per-app limits are soft limits. If your use case requires higher limits, contact Cloudflare support. ::: +## Evaluation context limits + +The following limits apply to each evaluation context: + +| Feature | Limit | Scope | +| ------------------------------- | ------------- | ---------------------------------------- | +| Top-level attributes | 100 | All evaluation paths | +| Top-level key length | 1–256 chars | All evaluation paths | +| Value nesting depth | 10 levels | All evaluation paths | +| Serialized UTF-8 JSON size | 32 KiB | All evaluation paths | +| Values visited during traversal | 10,000 values | Data-plane HTTP and Workers binding only | + +Flagship control-plane `POST` endpoints do not apply the 10,000-value traversal limit. Other context limits still apply. + ## Notes -- Condition nesting depth counts from the top-level condition group. A flat list of conditions (no nesting) has a depth of 1. +- Condition grouping depth counts from the top-level condition group. A flat list of conditions has a depth of one. +- Context value nesting and attribute path traversal use separate limits. Targeting attribute paths can contain up to five segments. - Flag keys, app names, condition set names, and variant keys can contain letters, numbers, hyphens, and underscores. - All variants on a flag must use the same value type: boolean, string, number, or JSON. - Flag configuration size refers to the total serialized size of all flags within a single app, including their variants and rules. diff --git a/src/content/docs/flagship/reference/wrangler-commands.mdx b/src/content/docs/flagship/reference/wrangler-commands.mdx index 5eb1edaa143..b6b7de3c555 100644 --- a/src/content/docs/flagship/reference/wrangler-commands.mdx +++ b/src/content/docs/flagship/reference/wrangler-commands.mdx @@ -224,6 +224,7 @@ Conditions compare attributes from the evaluation context with rule values. The ```sh --rule "serve=on; when=plan equals pro" --rule "serve=on; when=country in [US,CA,UK]" +--rule "serve=on; when=tags has beta" --rule "serve=off; when=email ends_with @example.com" --rule "serve=strict; when=score less_than 20" ``` @@ -248,7 +249,7 @@ Use `AND` and `OR` to combine conditions. `AND` has higher precedence than `OR`. --rule 'serve=on; when=country in ["US","CA"]' ``` -For `in` and `not_in`, pass a bracketed list. Wrangler rejects malformed lists, empty list items, unterminated quotes, and unbalanced brackets before sending the request. +For `in` and `not_in`, pass a bracketed list. For `has` and `not_has`, pass the scalar array member to find. Wrangler rejects malformed lists, empty list items, unterminated quotes, and unbalanced brackets before sending the request. Use `--rule-json` for deeply nested logical groups or if you prefer to pass the API rule shape directly. Wrangler validates `--rule-json` against the Flagship rule schema and rejects unknown or conflicting fields. @@ -578,6 +579,8 @@ wrangler flagship flags evaluate premium-banner \ Context values are sent to the evaluation endpoint as strings. Use the same attribute names that your targeting rules expect. +JSON-looking `--context` values remain strings. Wrangler does not create nested objects or arrays from these values. + Evaluation output includes: | Field | Description | diff --git a/src/content/docs/flagship/sdk/client-provider.mdx b/src/content/docs/flagship/sdk/client-provider.mdx index cc2cfca4b45..dbd559e7746 100644 --- a/src/content/docs/flagship/sdk/client-provider.mdx +++ b/src/content/docs/flagship/sdk/client-provider.mdx @@ -80,6 +80,12 @@ if (showBanner) { | `retryDelay` | `number` | No | Delay between retries in milliseconds. Defaults to `1000` and is capped at `30000`. | | `prefetchFlags` | `string[]` | Yes | Flag keys to fetch on initialization and on every context change. Flags not in this list return `FLAG_NOT_FOUND` at evaluation time. | +## Evaluation context + +The client provider sends context through scalar `GET` query parameters. Structured object and array values are not supported. + +Use strings, numbers, and booleans for context attributes. JSON-looking strings remain strings and do not create nested context. + ## When to use the client provider Use the client provider in browser applications, single-page apps, or any client-side JavaScript environment. diff --git a/src/content/docs/flagship/sdk/go.mdx b/src/content/docs/flagship/sdk/go.mdx index eeadf72ce55..bafa00338b7 100644 --- a/src/content/docs/flagship/sdk/go.mdx +++ b/src/content/docs/flagship/sdk/go.mdx @@ -121,4 +121,4 @@ The cache is per-provider instance, guarded by a mutex for concurrent use, and c ## Evaluation context -Context attributes are sent as URL query parameters. Supported values are strings, numeric types, booleans, and `time.Time`. `nil` values are skipped. Maps, slices, structs, and other complex values return `INVALID_CONTEXT` through OpenFeature and do not trigger an HTTP request. +Context attributes are sent as scalar `GET` query parameters. Supported values are strings, numeric types, booleans, and `time.Time`. `nil` values are skipped. Maps, slices, structs, and other complex values return `INVALID_CONTEXT` through OpenFeature and do not trigger an HTTP request. diff --git a/src/content/docs/flagship/sdk/index.mdx b/src/content/docs/flagship/sdk/index.mdx index c6c4053d864..2ed6cbce305 100644 --- a/src/content/docs/flagship/sdk/index.mdx +++ b/src/content/docs/flagship/sdk/index.mdx @@ -39,6 +39,22 @@ Flagship SDKs are organized by language. The TypeScript SDK has separate setup g If you are running inside a Cloudflare Worker, the [binding](/flagship/binding/) is the recommended approach because it avoids HTTP overhead. You can also [pass the binding to the OpenFeature SDK](/flagship/sdk/server-provider/) to get the best of both. Use the SDK without a binding when running in non-Worker runtimes like Node.js or the browser. ::: +## Context support + +Context support depends on the evaluation client and transport: + +| Client or API | Context support | +| --------------------------- | ----------------------------------------------------------------- | +| Direct Workers binding | Recursive JSON values; valid `Date` values normalize to strings | +| TypeScript server provider | Strings, numbers, booleans, and `Date`; rejects objects or arrays | +| TypeScript client provider | Scalar GET query values; structured values are unsupported | +| Python provider | Scalar GET query values and `datetime`; rejects complex values | +| Go provider | Scalar GET query values and `time.Time`; rejects complex values | +| Wrangler | String values from each `--context` argument | +| Evaluation API `POST` | Recursive JSON values | + +Use the direct Workers binding for structured context inside Workers. Use the [evaluation API `POST` endpoint](/flagship/reference/evaluation-api/#send-structured-json-context) for structured context over HTTP. + ## Installation For TypeScript server-side usage: diff --git a/src/content/docs/flagship/sdk/python.mdx b/src/content/docs/flagship/sdk/python.mdx index 510fa21aff7..06ead79c522 100644 --- a/src/content/docs/flagship/sdk/python.mdx +++ b/src/content/docs/flagship/sdk/python.mdx @@ -94,7 +94,7 @@ Cached values may be stale until the TTL expires. Keep the TTL short for flags t ## Evaluation context -Context attributes are sent as URL query parameters. Supported values are strings, integers, floats, booleans, and `datetime` values. Dictionaries, lists, tuples, and other complex values raise `InvalidContextError`. +Context attributes are sent as scalar `GET` query parameters. Supported values are strings, integers, floats, booleans, and `datetime` values. Dictionaries, lists, tuples, and other complex values raise `InvalidContextError`. ## Async evaluation diff --git a/src/content/docs/flagship/sdk/server-provider.mdx b/src/content/docs/flagship/sdk/server-provider.mdx index dc3d56e5499..7db1341ef67 100644 --- a/src/content/docs/flagship/sdk/server-provider.mdx +++ b/src/content/docs/flagship/sdk/server-provider.mdx @@ -120,7 +120,9 @@ OpenFeature uses an evaluation context to pass user attributes to the flag provi Pass additional attributes alongside `targetingKey` to match [targeting rules](/flagship/targeting/). For example, you can include `plan`, `country`, or any custom attribute your rules reference. -Use primitive context values such as strings, numbers, booleans, and `Date` objects. The provider rejects objects and arrays as invalid context. +Use primitive context values such as strings, numbers, booleans, and `Date` objects. The provider rejects objects and arrays as invalid context, including when configured with a binding. + +The provider sends HTTP evaluations through scalar `GET` query parameters. Use the binding directly when your Worker needs structured context. diff --git a/src/content/docs/flagship/targeting/index.mdx b/src/content/docs/flagship/targeting/index.mdx index 58968a5b2a6..efef0e76a93 100644 --- a/src/content/docs/flagship/targeting/index.mdx +++ b/src/content/docs/flagship/targeting/index.mdx @@ -31,15 +31,37 @@ A rule consists of: Each condition compares an attribute from the evaluation context against a value using an operator: - **Attribute** — The context key to evaluate (for example, `userId`, `country`, `plan`). -- **Operator** — The comparison to perform. Flagship supports [11 operators](/flagship/targeting/operators/). +- **Operator** — The comparison to perform. Flagship supports [13 operators](/flagship/targeting/operators/). - **Value** — The value to compare against. Can be a string, number, or array depending on the operator. If the evaluation context does not include the attribute referenced by a condition, that condition does not match. +The following rule matches an enterprise profile with the `beta` tag: + +```json +{ + "conditions": [ + { + "attribute": "profile.plan", + "operator": "equals", + "value": "enterprise" + }, + { "attribute": "tags", "operator": "has", "value": "beta" } + ], + "serve_variation": "on" +} +``` + +Dot-separated attributes traverse nested context objects. Paths can contain at most five segments. + +A literal top-level key takes precedence over path traversal. For example, `"profile.plan"` uses that exact top-level key when both forms exist. + ## Logical grouping Conditions within a rule can be grouped with `AND`/`OR` operators and nested up to five levels deep. +Condition grouping depth is separate from context value nesting. It is also separate from the five-segment attribute path limit. + For example, to target enterprise users in the US or Canada: - `AND`: diff --git a/src/content/docs/flagship/targeting/operators.mdx b/src/content/docs/flagship/targeting/operators.mdx index a11ebe078a4..5815a243d88 100644 --- a/src/content/docs/flagship/targeting/operators.mdx +++ b/src/content/docs/flagship/targeting/operators.mdx @@ -1,30 +1,32 @@ --- pcx_content_type: reference title: Operators -description: Reference for the 11 comparison operators available in Flagship targeting rule conditions, including equality, comparison, string, and array operators. +description: Reference for the 13 comparison operators available in Flagship targeting rule conditions, including equality, comparison, string, and array operators. sidebar: order: 1 products: - flagship --- -Flagship supports 11 comparison operators for targeting rule conditions. Each operator compares an attribute from the [evaluation context](/flagship/concepts/#evaluation-context) against a specified value. +Flagship supports 13 comparison operators for targeting rule conditions. Each operator compares an attribute from the [evaluation context](/flagship/concepts/#evaluation-context) against a specified value. ## Operator reference -| Operator | Description | Example | Value type | -| ------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------ | ------------------------- | -| `equals` | Returns true if the attribute value matches the specified value. | `country equals "US"` | String | -| `not_equals` | Returns true if the attribute value does not match the specified value. | `plan not_equals "free"` | String | -| `greater_than` | Returns true if the attribute value is greater than the specified value. | `age greater_than 18` | Number, ISO 8601 datetime | -| `less_than` | Returns true if the attribute value is less than the specified value. | `loginCount less_than 5` | Number, ISO 8601 datetime | -| `greater_than_or_equals` | Returns true if the attribute value is greater than or equal to the specified value. | `score greater_than_or_equals 90` | Number, ISO 8601 datetime | -| `less_than_or_equals` | Returns true if the attribute value is less than or equal to the specified value. | `createdAt less_than_or_equals "2025-01-01T00:00:00Z"` | Number, ISO 8601 datetime | -| `contains` | Returns true if the attribute value contains the specified substring. | `email contains "@cloudflare.com"` | String | -| `starts_with` | Returns true if the attribute value starts with the specified prefix. | `path starts_with "/api/v2"` | String | -| `ends_with` | Returns true if the attribute value ends with the specified suffix. | `domain ends_with ".dev"` | String | -| `in` | Returns true if the attribute value is in the specified array. | `country in ["US", "CA", "UK"]` | Array | -| `not_in` | Returns true if the attribute value is not in the specified array. | `userId not_in ["blocked-1", "blocked-2"]` | Array | +| Operator | Description | Example | Rule value type | +| ------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------ | ------------------------- | +| `equals` | Returns true if the attribute matches the specified scalar. | `country equals "US"` | Scalar | +| `not_equals` | Returns true if the attribute differs from the specified scalar. | `plan not_equals "free"` | Scalar | +| `greater_than` | Returns true if the attribute value is greater than the specified value. | `age greater_than 18` | Number, ISO 8601 datetime | +| `less_than` | Returns true if the attribute value is less than the specified value. | `loginCount less_than 5` | Number, ISO 8601 datetime | +| `greater_than_or_equals` | Returns true if the attribute value is greater than or equal to the specified value. | `score greater_than_or_equals 90` | Number, ISO 8601 datetime | +| `less_than_or_equals` | Returns true if the attribute value is less than or equal to the specified value. | `createdAt less_than_or_equals ` | Number, ISO 8601 datetime | +| `contains` | Returns true if the attribute value contains the specified substring. | `email contains "@cloudflare.com"` | String | +| `starts_with` | Returns true if the attribute value starts with the specified prefix. | `path starts_with "/api/v2"` | String | +| `ends_with` | Returns true if the attribute value ends with the specified suffix. | `domain ends_with ".dev"` | String | +| `in` | Returns true if the scalar attribute is in the specified array. | `country in ["US", "CA", "UK"]` | Array | +| `not_in` | Returns true if the scalar attribute is not in the specified array. | `userId not_in ["blocked-1", "blocked-2"]` | Array | +| `has` | Returns true if the array attribute contains the specified scalar. | `tags has "beta"` | Scalar | +| `not_has` | Returns true if the array attribute does not contain the specified scalar. | `roles not_has "contractor"` | Scalar | ## Operator categories @@ -32,13 +34,13 @@ Flagship supports 11 comparison operators for targeting rule conditions. Each op `equals`, `not_equals` -Use these operators for exact string matching. The comparison is case-sensitive. +Use these operators for exact scalar matching. String comparisons are case-sensitive. ### Comparison operators `greater_than`, `less_than`, `greater_than_or_equals`, `less_than_or_equals` -These operators work with numeric values and ISO 8601 datetime strings. When comparing datetimes, provide the value in ISO 8601 format (for example, `"2025-01-01T00:00:00Z"`). +These operators work with numeric values and ISO 8601 datetime strings. Provide datetime values in ISO 8601 format. ### String operators @@ -48,6 +50,16 @@ These operators perform substring matching against the attribute value. All stri ### Array operators -`in`, `not_in` +`in`, `not_in`, `has`, `not_has` -The value must be an array. Flagship checks whether the attribute value is a member of the specified array. +For `in` and `not_in`, the rule value must be an array. Flagship checks whether a scalar attribute belongs to that array. + +For `has` and `not_has`, the context attribute must be an array. Flagship checks whether that array contains the rule's scalar value. + +## Context value behavior + +Missing attributes never match, including with negative operators. A `null` attribute only matches `not_equals` and `not_in` conditions. + +Arrays support `has` and `not_has`. Object and array attributes do not act as scalar operands for other operators. + +Dot-separated attributes can traverse up to five object segments. A literal top-level key takes precedence over nested traversal. diff --git a/src/content/docs/flagship/targeting/percentage-rollouts.mdx b/src/content/docs/flagship/targeting/percentage-rollouts.mdx index 1393e78c08e..37475c5b356 100644 --- a/src/content/docs/flagship/targeting/percentage-rollouts.mdx +++ b/src/content/docs/flagship/targeting/percentage-rollouts.mdx @@ -22,22 +22,30 @@ For example, a rule can target users on the `enterprise` plan, then serve the ne { "priority": 1, "conditions": [ - { "attribute": "plan", "operator": "equals", "value": "enterprise" } + { + "attribute": "profile.plan", + "operator": "equals", + "value": "enterprise" + } ], "serve_variation": "on", "rollout": { "percentage": 10, - "attribute": "userId" + "attribute": "profile.id" } } ``` +The `profile.id` path resolves an ID from nested context. A bucketing path can contain at most five segments. + The `reason` in evaluation details is `SPLIT` when a percentage rollout serves the variant. ## Sticky bucketing Flagship uses consistent hashing on a configurable attribute to assign users to a rollout bucket. The same user always receives the same flag value for a given rollout configuration. This ensures a consistent experience across repeated evaluations. +For sticky bucketing, the resolved attribute must be a string, number, or boolean. Flagship converts that scalar to a stable hash input. + By default, the bucketing attribute is `targetingKey`. You can configure which attribute to use for bucketing when you set up the rollout in the dashboard. Rollout buckets are independent across accounts and flags. The same identifier may fall into different buckets for different flags, which helps avoid correlated rollouts across unrelated features. @@ -53,12 +61,14 @@ Choose a bucketing attribute based on what should remain stable: For most feature releases and experiments, use a stable user or account identifier. Use request-level values only when changing between requests is acceptable, such as for traffic sampling. -:::caution[Random assignment without targetingKey] -If `targetingKey` is not present in the evaluation context and no alternative bucketing attribute is configured, Flagship cannot produce a stable hash. In this case the rollout bucket is assigned randomly on each evaluation, meaning the same user may receive different flag values across requests. +:::caution[Random assignment without a bucketing value] +If the bucketing path is missing or resolves to `null`, Flagship assigns a random bucket for each evaluation. The same user may receive different values across requests. Always provide a stable `targetingKey` (or configure a consistent bucketing attribute) to guarantee sticky bucketing. ::: +If the bucketing path resolves to an array or object, the rule fails. Evaluation then continues to the next rule. + ## Common rollout patterns ### Progressive rollout From d113f34b99b338c5319955bb44584ec079dcfaa9 Mon Sep 17 00:00:00 2001 From: Brendan Irvine-Broque Date: Sat, 17 Oct 2026 08:35:39 -0700 Subject: [PATCH 2/4] [Flagship] Correct structured context docs and add changelog --- ...26-10-23-structured-evaluation-context.mdx | 29 +++++++++++++++++++ src/content/docs/flagship/binding/types.mdx | 11 ++----- .../flagship/reference/evaluation-reasons.mdx | 6 +--- .../docs/flagship/sdk/client-provider.mdx | 4 +-- src/content/docs/flagship/sdk/go.mdx | 4 ++- src/content/docs/flagship/sdk/index.mdx | 24 ++++++++------- src/content/docs/flagship/sdk/python.mdx | 4 ++- .../docs/flagship/sdk/server-provider.mdx | 8 ++--- 8 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 src/content/changelog/flagship/2026-10-23-structured-evaluation-context.mdx diff --git a/src/content/changelog/flagship/2026-10-23-structured-evaluation-context.mdx b/src/content/changelog/flagship/2026-10-23-structured-evaluation-context.mdx new file mode 100644 index 00000000000..0ba97f834e0 --- /dev/null +++ b/src/content/changelog/flagship/2026-10-23-structured-evaluation-context.mdx @@ -0,0 +1,29 @@ +--- +title: Target Flagship feature flags with structured evaluation context +description: Use nested objects, arrays, and null in Flagship evaluation context, with dot-path targeting and array membership operators. +date: 2026-10-23 +--- + +import { TypeScriptExample } from "~/components"; + +[Flagship](/flagship/) supports nested objects, arrays, and `null` in evaluation context. Pass user profiles and tag lists directly, then target nested attributes without flattening your application data. + +For example, pass a profile and tags when evaluating a flag through the Workers binding: + + + +```ts +const enabled = await env.FLAGS.getBooleanValue("new-checkout", false, { + targetingKey: "user-42", + profile: { plan: "enterprise" }, + tags: ["beta", "early-access"], +}); +``` + + + +A targeting rule can combine `profile.plan equals "enterprise"` with `tags has "beta"` to serve the feature to enterprise users in the beta group. Use dot-separated paths for nested attributes and the `has` and `not_has` operators for array membership. Percentage rollouts can also use a nested identifier, such as `profile.id`, for consistent user assignment. + +Structured context works through the Workers binding, the evaluation REST API with a JSON `POST` body, and the TypeScript, Python, and Go OpenFeature SDKs from version `0.6.0`. The SDKs automatically use `POST` when HTTP evaluation requires structured context. + +Refer to [targeting rules](/flagship/targeting/), the [evaluation API](/flagship/reference/evaluation-api/), and [SDK context support](/flagship/sdk/#context-support) for usage and supported values. diff --git a/src/content/docs/flagship/binding/types.mdx b/src/content/docs/flagship/binding/types.mdx index 30029837099..f7676d273fc 100644 --- a/src/content/docs/flagship/binding/types.mdx +++ b/src/content/docs/flagship/binding/types.mdx @@ -8,7 +8,7 @@ products: - flagship --- -The Flagship binding runtime uses the following TypeScript shapes. The published Workers type declarations do not yet include every runtime value. +The Flagship binding uses the following TypeScript types. Generate binding types with `wrangler types` or use the `@cloudflare/workers-types` package. ## `Flagship` @@ -35,19 +35,14 @@ type FlagshipEvaluationContextValue = | FlagshipEvaluationContextValue[] | { [key: string]: FlagshipEvaluationContextValue }; -type FlagshipEvaluationContext = Record< - string, - FlagshipEvaluationContextValue ->; +type FlagshipEvaluationContext = Record; ``` Numbers must be finite. The binding rejects `NaN`, `Infinity`, unsupported objects, and cyclic values. The runtime also accepts valid JavaScript `Date` values. The binding normalizes each date to an ISO 8601 string. Invalid dates produce an `INVALID_CONTEXT` error. -The public declarations from `wrangler types` and `@cloudflare/workers-types` still define context as `Record`. Therefore, TypeScript can reject structured context that the runtime accepts. [workerd pull request #7462](https://github.com/cloudflare/workerd/pull/7462) tracks the recursive public declarations and remains open. - -Until those declarations are published, TypeScript requires an explicit cast for structured context. This declaration mismatch does not change runtime support. Serialize `Date` values yourself when you need declaration-compatible code. +Serialize `Date` values to ISO 8601 strings before passing them to the binding in TypeScript. The context type describes JSON values and does not include `Date`. ## `FlagshipEvaluationDetails` diff --git a/src/content/docs/flagship/reference/evaluation-reasons.mdx b/src/content/docs/flagship/reference/evaluation-reasons.mdx index 262ec4068e8..3f7a29c34c1 100644 --- a/src/content/docs/flagship/reference/evaluation-reasons.mdx +++ b/src/content/docs/flagship/reference/evaluation-reasons.mdx @@ -16,7 +16,6 @@ When you evaluate a flag using the binding's `*Details` methods or the OpenFeatu | Reason | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `STATIC` | The enabled flag has no targeting rules. Its default variant was returned. | | `TARGETING_MATCH` | A targeting rule's conditions matched the evaluation context, and the rule's variant was returned. | | `SPLIT` | A targeting rule with a percentage rollout matched. The user fell within the rollout percentage and received the rule's variant. | | `STATIC` | The flag is enabled and has no targeting rules, so its default variant was returned. | @@ -33,7 +32,7 @@ When an evaluation error occurs, the method returns the default value you provid | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `TYPE_MISMATCH` | The flag's variant type does not match the requested type. For example, calling `getBooleanValue` on a flag whose variant is a string. The default value is returned. | | `FLAG_NOT_FOUND` | The specified flag key does not exist in the app. The default value is returned. | -| `INVALID_CONTEXT` | The context has an invalid shape or unsupported value, contains a non-finite number or cycle, or exceeds a limit. | +| `INVALID_CONTEXT` | The context has an invalid shape or unsupported value, contains a non-finite number or cycle, or exceeds a limit. | | `PARSE_ERROR` | The SDK received an invalid evaluation response. The default value is returned. | | `GENERAL` | An unexpected error occurred during evaluation, such as a timeout or network failure. The default value is returned. | @@ -55,9 +54,6 @@ const details = await env.FLAGS.getBooleanDetails("my-feature", false, { }); switch (details.reason) { - case "STATIC": - console.log("No targeting rules, using default variant"); - break; case "TARGETING_MATCH": console.log(`Matched targeting rule, variant: ${details.variant}`); break; diff --git a/src/content/docs/flagship/sdk/client-provider.mdx b/src/content/docs/flagship/sdk/client-provider.mdx index dbd559e7746..c6166842d3e 100644 --- a/src/content/docs/flagship/sdk/client-provider.mdx +++ b/src/content/docs/flagship/sdk/client-provider.mdx @@ -82,9 +82,9 @@ if (showBanner) { ## Evaluation context -The client provider sends context through scalar `GET` query parameters. Structured object and array values are not supported. +From version `0.6.0`, the client provider accepts strings, finite numbers, booleans, `null`, nested objects, and arrays. It normalizes valid `Date` values to ISO 8601 strings, including within objects and arrays. -Use strings, numbers, and booleans for context attributes. JSON-looking strings remain strings and do not create nested context. +During initialization and context changes, the provider sends structured context using `POST` with a JSON body. Context containing only strings, numbers, booleans, or dates uses `GET` query parameters. JSON-looking strings remain strings and do not create nested context. ## When to use the client provider diff --git a/src/content/docs/flagship/sdk/go.mdx b/src/content/docs/flagship/sdk/go.mdx index bafa00338b7..d7d0d237de0 100644 --- a/src/content/docs/flagship/sdk/go.mdx +++ b/src/content/docs/flagship/sdk/go.mdx @@ -121,4 +121,6 @@ The cache is per-provider instance, guarded by a mutex for concurrent use, and c ## Evaluation context -Context attributes are sent as scalar `GET` query parameters. Supported values are strings, numeric types, booleans, and `time.Time`. `nil` values are skipped. Maps, slices, structs, and other complex values return `INVALID_CONTEXT` through OpenFeature and do not trigger an HTTP request. +From version `0.6.0`, context attributes can contain strings, finite numeric values, booleans, `nil`, maps with string keys, slices, and arrays. The provider recursively normalizes `time.Time` values to RFC 3339 strings. + +The provider sends structured context using `POST` with a JSON body. Context containing only strings, numbers, booleans, or times uses `GET` query parameters. Unsupported values, such as other structs, and non-finite numbers return `INVALID_CONTEXT` through OpenFeature without triggering an HTTP request. diff --git a/src/content/docs/flagship/sdk/index.mdx b/src/content/docs/flagship/sdk/index.mdx index 2ed6cbce305..ad72612f884 100644 --- a/src/content/docs/flagship/sdk/index.mdx +++ b/src/content/docs/flagship/sdk/index.mdx @@ -41,19 +41,21 @@ If you are running inside a Cloudflare Worker, the [binding](/flagship/binding/) ## Context support -Context support depends on the evaluation client and transport: +The TypeScript, Python, and Go SDKs support structured context from version `0.6.0`. Context support depends on the evaluation client and transport: -| Client or API | Context support | -| --------------------------- | ----------------------------------------------------------------- | +| Client or API | Context support | +| -------------------------- | ----------------------------------------------------------------- | | Direct Workers binding | Recursive JSON values; valid `Date` values normalize to strings | -| TypeScript server provider | Strings, numbers, booleans, and `Date`; rejects objects or arrays | -| TypeScript client provider | Scalar GET query values; structured values are unsupported | -| Python provider | Scalar GET query values and `datetime`; rejects complex values | -| Go provider | Scalar GET query values and `time.Time`; rejects complex values | -| Wrangler | String values from each `--context` argument | -| Evaluation API `POST` | Recursive JSON values | - -Use the direct Workers binding for structured context inside Workers. Use the [evaluation API `POST` endpoint](/flagship/reference/evaluation-api/#send-structured-json-context) for structured context over HTTP. +| TypeScript server provider | Recursive JSON values and `Date`, with binding or HTTP evaluation | +| TypeScript client provider | Recursive JSON values and `Date` | +| Python provider | Recursive JSON values and `datetime` | +| Go provider | Recursive JSON values and `time.Time` | +| Wrangler | String values from each `--context` argument | +| Evaluation API `POST` | Recursive JSON values | + +The SDKs send structured context over HTTP using `POST`. Context containing only strings, numbers, booleans, or dates uses `GET` query parameters. Dates normalize to strings, including within nested objects and arrays. + +Use the [evaluation API `POST` endpoint](/flagship/reference/evaluation-api/#send-structured-json-context) when sending structured context without an SDK. ## Installation diff --git a/src/content/docs/flagship/sdk/python.mdx b/src/content/docs/flagship/sdk/python.mdx index 06ead79c522..fa12f5a9d3c 100644 --- a/src/content/docs/flagship/sdk/python.mdx +++ b/src/content/docs/flagship/sdk/python.mdx @@ -94,7 +94,9 @@ Cached values may be stale until the TTL expires. Keep the TTL short for flags t ## Evaluation context -Context attributes are sent as scalar `GET` query parameters. Supported values are strings, integers, floats, booleans, and `datetime` values. Dictionaries, lists, tuples, and other complex values raise `InvalidContextError`. +From version `0.6.0`, context attributes can contain strings, integers, finite floats, booleans, `None`, dictionaries with string keys, lists, and tuples. The provider recursively normalizes `datetime` values to ISO 8601 strings. + +The provider sends structured context using `POST` with a JSON body. Context containing only strings, numbers, booleans, or datetimes uses `GET` query parameters. Unsupported values, non-finite numbers, and cycles raise `InvalidContextError`. ## Async evaluation diff --git a/src/content/docs/flagship/sdk/server-provider.mdx b/src/content/docs/flagship/sdk/server-provider.mdx index 7db1341ef67..5bb4630a1a0 100644 --- a/src/content/docs/flagship/sdk/server-provider.mdx +++ b/src/content/docs/flagship/sdk/server-provider.mdx @@ -120,17 +120,17 @@ OpenFeature uses an evaluation context to pass user attributes to the flag provi Pass additional attributes alongside `targetingKey` to match [targeting rules](/flagship/targeting/). For example, you can include `plan`, `country`, or any custom attribute your rules reference. -Use primitive context values such as strings, numbers, booleans, and `Date` objects. The provider rejects objects and arrays as invalid context, including when configured with a binding. +From version `0.6.0`, the provider accepts strings, finite numbers, booleans, `null`, nested objects, and arrays. It normalizes valid `Date` values to ISO 8601 strings, including within objects and arrays. Structured context works with both binding and HTTP evaluation. -The provider sends HTTP evaluations through scalar `GET` query parameters. Use the binding directly when your Worker needs structured context. +For HTTP evaluation, the provider uses `POST` with a JSON body when context includes objects, arrays, or `null`. Otherwise, it uses `GET` query parameters. Unsupported values, non-finite numbers, invalid dates, and cycles produce `INVALID_CONTEXT` errors. ```ts const value = await client.getBooleanValue("new-checkout", false, { targetingKey: "user-42", - plan: "enterprise", - country: "US", + profile: { plan: "enterprise" }, + tags: ["beta"], }); ``` From d503eb5e27367dd0d4d06deac40c6b8bd47b4bbb Mon Sep 17 00:00:00 2001 From: Madhu Balakrishna Date: Mon, 19 Oct 2026 11:46:01 +0530 Subject: [PATCH 3/4] [Flagship] Retain userId in structured context examples --- src/content/docs/flagship/binding/index.mdx | 1 + src/content/docs/flagship/concepts.mdx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/content/docs/flagship/binding/index.mdx b/src/content/docs/flagship/binding/index.mdx index 47f56a678f6..5508e5dc6d0 100644 --- a/src/content/docs/flagship/binding/index.mdx +++ b/src/content/docs/flagship/binding/index.mdx @@ -41,6 +41,7 @@ The binding provides type-safe methods for evaluating feature flags. If an evalu export default { async fetch(request: Request, env: Env): Promise { const enabled = await env.FLAGS.getBooleanValue("new-feature", false, { + userId: "user-42", profile: { plan: "enterprise" }, tags: ["beta"], }); diff --git a/src/content/docs/flagship/concepts.mdx b/src/content/docs/flagship/concepts.mdx index 2a88ca5cc01..06b66d92697 100644 --- a/src/content/docs/flagship/concepts.mdx +++ b/src/content/docs/flagship/concepts.mdx @@ -77,6 +77,7 @@ You pass the context as the third argument to evaluation methods on the binding: ```ts const value = await env.FLAGS.getBooleanValue("new-checkout", false, { + userId: "user-42", profile: { plan: "enterprise" }, tags: ["beta"], }); From 4c3c9bb734014640f1574758e7ea25039f5af040 Mon Sep 17 00:00:00 2001 From: Madhu Balakrishna Date: Mon, 19 Oct 2026 14:53:57 +0530 Subject: [PATCH 4/4] [Flagship] Remove note on control-plane POST traversal limit exemption --- src/content/docs/flagship/reference/limits.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/content/docs/flagship/reference/limits.mdx b/src/content/docs/flagship/reference/limits.mdx index d2eb500a524..a315dc914cd 100644 --- a/src/content/docs/flagship/reference/limits.mdx +++ b/src/content/docs/flagship/reference/limits.mdx @@ -40,8 +40,6 @@ The following limits apply to each evaluation context: | Serialized UTF-8 JSON size | 32 KiB | All evaluation paths | | Values visited during traversal | 10,000 values | Data-plane HTTP and Workers binding only | -Flagship control-plane `POST` endpoints do not apply the 10,000-value traversal limit. Other context limits still apply. - ## Notes - Condition grouping depth counts from the top-level condition group. A flat list of conditions has a depth of one.