decide() — plan, device_type, cart_value, user_id. Parameters are what the SDK gets back; attributes are what it sends in.
Why register attributes
Registration matters most for strictness: SDKs evaluate conditions with no coercion. A context value
42 never matches a condition value "42". The registry is where the dashboard and API learn which one to store.
Types and formats
format only applies to string attributes. Timestamps must be numbers in context — the SDK relational operators compare numbers only; the dashboard’s date picker converts to epoch milliseconds for you.
values
For format: enum, values is the list of legal values (1–500 entries). Each value can carry a label and description. A condition value outside the list is a validation finding.
For format: country, values is optional: leave it empty to accept any ISO 3166-1 alpha-2 code, or list a subset. Condition values are upper-cased and checked against the code list.
range
For number attributes, range: [min, max] (with min < max) bounds the values a condition may use. A condition value outside the range is a validation finding.
identifier
identifier: true marks an attribute that may serve as a unit key or entity key — user_id, anonymous_id, merchant_id. Identifiers default to logging: never. Traffical registers one identifier attribute per entity definition automatically, so your project’s unit keys are in the registry from the start.
logging
Context is evaluated inside the SDK. Whether a value is also written to the decision events and exposure events the SDK sends is decided per attribute:
Use
never for anything you target on but must not store — identifiers, email addresses, anything personal. allowed is the usual choice. always suits low-cardinality segmentation keys you want on every event, such as device_type.
Contextual bandits read their features from the same list, so an attribute’s logging also determines whether a model can learn from it. See the context logging allowlist.
breakdown
breakdown: true marks an attribute as a dimension to split measured results by. The flag is stored today; result breakdowns by attribute ship in a later release. Setting it now means nothing has to be re-registered when they do.
System attributes
Keys that start with$ are reserved for Traffical. They are registered in every project, and you can edit only their label, description, logging, and breakdown. You cannot declare your own $ keys.
$unit_key — the unit this layer buckets on
Every layer hashes one context field to assign buckets — user_id for one layer, merchant_id for a layer with a different entity. $unit_key always means “that field, whatever it is in this layer”. Traffical replaces it with the concrete field name when it builds the config bundle, so the SDK sees an ordinary condition and no SDK change is involved.
The typical use is a test-users policy: a policy at the top of the layer with one 100% allocation carrying the overrides you want to see, and the condition
$unit_key supports eq, neq, in, and nin. Its values are coerced to the type of the layer’s entity key (integer entities compare as numbers, everything else as strings). ID lists ship in the config bundle in clear text, so keep them to a handful of developer IDs.

The condition editor with $unit_key under System and a registered enum attribute
$env — resolved per environment
$env is an enum of your project’s environment IDs. A condition on $env is resolved when the bundle is built for each environment, not in the SDK: if it holds for that environment the condition is dropped, if not the policy is left out of that environment’s bundle entirely. The SDK never has to send $env.
staging only. Supported operators: eq, neq, in, nin.
$country — set on server-side resolution
When you resolve through the /v1/resolve endpoint, Traffical fills in $country from the request’s origin (ISO 3166-1 alpha-2) if your context does not already carry one. A value you send yourself is never overwritten. In bundle mode the SDK evaluates locally and nothing is injected, so send $country yourself if you need it there.
Web and mobile device keys
The remaining system attributes are filled by the SDKs’ opt-in collectors — the auto-attributes plugin in the browser SDK, and the default device-info providers on iOS and React Native. Register nothing: the keys exist in every project.
Which keys are actually sent depends on the platform: the browser plugin sends the browser, page, UTM, and locale keys; the mobile providers send
$os, $os_version, $app_version, $device_model, $device_type, $locale, and $timezone. See each SDK page for the exact derivation.
Enforcement modes
Attributes never block traffic. Enforcement decides what happens when you save a policy whose conditions reference an unregistered key, use an operator the attribute’s type does not allow, or carry a value that fails validation. Set it under Settings → Attribute enforcement:
Unregistered keys keep working end to end in every mode: the SDK evaluates them as before, and the registry is never consulted at resolution time. Coercion only applies to registered keys — with no declared type there is nothing to coerce to, so the value is stored as you entered it.
Discovering attributes from traffic
Projects on Traffical’s managed warehouse get a Seen in traffic, not registered panel on the Attributes page. Once a day, Traffical scans the context on decision and exposure events from the last 7 days and lists each top-level key with how many decisions carried it, its inferred type, and its most common values. Register… opens the add sheet pre-filled from those stats; Dismiss hides a key you do not want to register. Projects that bring their own warehouse have no discovery panel — register attributes manually or fromconfig.yaml.

The Attributes list with the discovery panel
In the dashboard
Open Attributes in the project sidebar, directly after Parameters. Add attribute opens a sheet with key, label, description, type, format, values, range, identifier, logging, breakdown, and training hints. System rows show a lock and allow only label, description, logging, and breakdown edits. Attributes pushed fromconfig.yaml are synced and read-only in the dashboard, like parameters.
Config as code
Declare attributes in.traffical/config.yaml and push them with the CLI. traffical push sends attributes before parameters, property groups, and events, so anything validated later in the run already sees the registry.
^[A-Za-z_][A-Za-z0-9_.]*$ and be at most 128 characters. $ keys cannot be declared here: pull never writes them and push rejects them. See the configuration file reference for every field.
Typed context
traffical generate-types reads the project’s registry and emits a TrafficalContext interface alongside the parameter and event types:
number. The index signature keeps unregistered keys compiling, so the type catches typos and wrong enum values without banning ad-hoc keys:
Next steps
Policies
Every operator, strict typing, and the field lookup rule.
Configuration file
The
attributes: block field by field.Browser SDK
Collect browser, page, and UTM attributes automatically.
Optimization
Context logging and contextual bandits.