Entity & Page Context
Feature flags can target who is viewing a page (user context) and what is on the page (entity context). Both use the same filter model, but they answer different questions and are evaluated at different call sites.
User context vs entity context
| User context | Entity context | |
|---|---|---|
| Built-in kind | User (identity, groups, claims) | Named kinds you define (Order, Product, Account, …) |
| Scope | One signed-in user per browser/session | One domain object per evaluation (row, card, detail page) |
| Set via | setContext() / server HttpContext | Pass the entity on each check or widget |
| Rollout / percentage | Yes — percentage is user-only | No — never "% of entities" |
| Dashboard catalog sync | N/A | Trusted/server SDKs only (startup registration) |
User context drives rollouts, segments, User Claims, and percentage filters. Set it once per session:
await toggly.setContext({
identity: user.id,
groups: user.groups,
claims: { plan: user.plan },
});
Entity context drives Context Property filters against a specific instance — the order on this detail page, the product in this table row. Pass it per evaluation, not on global setContext():
// List page: each row can resolve differently, no extra network call
orders.map((order) =>
Toggly.isFeatureOn('OrderBadge', order, 'Order') ? <Badge /> : null
);
Entity context is per widget / per check, not process-wide. Do not attach entity attributes to global user setContext() — that would apply one entity to the whole app.
How evaluation combines user and entity rules
For a feature with both user filters and Context Property filters:
- User filters (targeting, percentage, claims, browser, …) run on the worker when building evaluated-signed payloads, using the user's identity from the GET query string.
- If user filters fail → result is
false(no gate object). - If user filters pass and entity rules remain → result is an EntityGate object for offline client evaluation.
- If user filters pass and there are no entity rules → result is
true.
On the client, the final boolean is:
enabled = userPassed (from server) AND entityRulesPass (local, when gate present)
Percentage / rollout is user-only. Entity rules never participate in percentage bucketing.
Mixed evaluated definitions (boolean | EntityGate)
Client SDKs fetch evaluated-signed definitions as a map of feature key → value:
type EvaluatedDefinitionValue = boolean | EntityGate;
interface EntityGate {
requirement: 'all' | 'any';
rules: EntityGateRule[]; // property, op, value, optional type
}
type EvaluatedDefinitions = Record<string, EvaluatedDefinitionValue>;
Examples:
| Server value | Meaning |
|---|---|
false | Off for this user (user filters failed or feature disabled) |
true | On for this user; no entity rules to check locally |
{ requirement, rules } | User filters passed; evaluate rules locally against the entity you pass |
Offline local evaluation
After one evaluated-signed fetch, list pages evaluate entity gates locally — different rows can show different UI with no extra network per row. The worker pre-computes user-side filters; the client applies entity rules from the cached gate object.
Use resolveEvaluatedDefinition(value, entityContext) from @ops-ai/toggly-hooks-types (or your SDK wrapper):
import { resolveEvaluatedDefinition, registerContext } from '@ops-ai/toggly-hooks-types';
registerContext('Order', (order) => ({
kind: 'Order',
key: String(order.id),
attributes: { Status: order.status, Total: order.total },
}));
const enabled = resolveEvaluatedDefinition(
flags['OrderBadge'],
{ kind: 'Order', key: '7', attributes: { Status: 'red' } },
);
Fail closed without context
Evaluation fails closed when:
- The feature value is an EntityGate but you did not pass entity
context - The entity kind is unknown (no
registerContextmapper on client; unregistered type on server) - The mapper cannot produce attributes for the instance
Unknown or missing context → off, not "match any entity."
Old SDKs and truthy gate objects
Legacy clients that check defs[key] === true treat gate objects as off. Gate objects are not truthy — always use resolveEvaluatedDefinition or SDK helpers that call it.
Schema mapping: registerContext vs startup registration
Client SDKs — local registerContext only
Browser and mobile SDKs do not call the dashboard Contexts API. Register mappers locally so the SDK can turn domain objects into { kind, key, attributes }:
Toggly.registerContext('Order', (order) => ({
kind: 'Order',
key: String(order.id),
attributes: {
Status: order.status,
Total: order.total,
},
}));
// Shorthand: pass entity + kind; SDK runs the mapper
Toggly.isFeatureOn('OrderBadge', order, 'Order');
You can also pass a fully built TogglyEntityContext object as the second argument.
Trusted server SDKs — startup registration (.NET / Node)
Server SDKs register context schemas at startup (default on) so the Toggly dashboard Contexts page lists available kinds and properties for filter authoring:
services.AddTogglyEntityContext<Order>(
"Order",
p => p.Id.ToString(),
builder => builder
.KeyProperty("Id")
.Property("Status", "string")
.Property("Total", "number"));
Set RegisterContextsOnStartup = false in TogglySettings to disable catalog sync (evaluation still works; dashboard may not list the kind until registered manually).
Registration failures are logged only — they do not block app startup.
User traits on evaluated-signed GET (?u=, ?g=, claim.*) are not a separate entity evaluate POST. See Evaluated-signed responses.
List pages and detail pages
| Page type | Pattern |
|---|---|
| Detail | One entity instance → pass it to <Feature context={…}> or isFeatureOn(key, entity) |
| List | Map rows; each row passes its own entity; gates resolve from cached defs |
{orders.map((order) => (
<Feature key={order.id} featureKey="ExpressCheckout" context={order} contextKind="Order">
<ExpressButton order={order} />
</Feature>
))}
SDK guides
| SDK | Guide |
|---|---|
| .NET / Razor | Entity context (.NET) |
| JavaScript (core) | Vanilla JS — entity context |
| React | React — entity context |
| Angular | Angular — entity context |
| Vue | Vue — entity context |
| Svelte | Svelte — entity context |
| Astro | Astro — entity context |
| Gatsby | Gatsby — entity context |
| Docusaurus | Docusaurus — entity context |
| Next.js client | Next.js client |
| Next.js server | Next.js server |
| Next.js edge | Next.js edge (boolean collapse) |
| Nuxt client / server | Nuxt client, server |
| Remix | Remix client, server |
| React Native | React Native |
| Flutter | Flutter |
| iOS | iOS |
| Android | Android |
| Node.js | Node.js — entity context |
| Go | Go evaluation |
| Java | Java evaluation |
| Python | Python |
| Rust | Rust evaluation |
| Ruby | Ruby context |
| PHP | PHP |
Related
- Segments & Targeting — user segments and reusable filters
- Feature filters — Context Property and other filter types
- Evaluated-signed responses — user context on the GET and mixed defs payload