Skip to main content
Use this when a field on a user, organization, or connection should drive an FGA relation. Metadata is the most common case: a user’s department_id in app_metadata controlling what they can access, or an organization’s plan in metadata gating a feature. The same pattern works for any other attribute on these objects; see Other attributes. Auth0 doesn’t emit a dedicated event per field. Instead, user.updated, organization.updated, and connection.updated fire on any change to the object. The event carries the object’s entire new state in data.object, together with the state before the change in data.previous_object. The tuples that result from a change have to be derived from these two sources, and there are two ways to do it: Option 1 and Option 2, compared under Choosing an approach. This only works when the value maps onto a bounded, enumerable relation: a department, a cost center, a plan tier, something you’d be comfortable writing as sometype:{value} in your model. Free-form or high-cardinality values (an arbitrary text note, a user’s date of birth, anything with effectively unlimited distinct values) don’t translate into FGA tuples usefully. Use app_metadata, not user_metadata, for anything that grants access. user_metadata is intended for data the user can edit, so a permission derived from it can be changed by the user it applies to.

Model

The same shape applies to organization metadata: swap user/department for organization/plan, as in the subscription plan example.

Option 1: Diff against previous_object

Compare the field in data.object with the same field in data.previous_object, and act only when they differ. Use ?. so a user with no app_metadata yet doesn’t make the rule fail.
The two rules together cover every case: When the field changes from a to b, both rules fire on the same event. Every write and delete ignores conflicts (see Idempotent writes and deletes), so reprocessing an event is safe.

Option 2: Reconcile with tuple filters

Ignore previous_object and reconcile against what FGA holds now. On every update, the mapping engine reads the tuples matching the filter, compares them to the desired state in tuples, and writes the minimum changes. This is the reconcile pattern (read-diff-write), the same one used for the organization plan in the mapping language reference.
Both rules key off the same field, split by whether it’s present: the first reconciles a new value in; the second, guarded by the opposite condition, deletes it outright when the field is cleared. A reconciling filter can’t also be told “and if there’s nothing to write, delete instead”; that needs its own rule with action: delete. Because the second rule matches every user.updated event where the field is absent, it deletes department tuples for all those users, including ones your application wrote. This is the incompatibility noted in the table above.

Choosing an approach

If Auth0 is the only writer of the relation and you’d rather it correct itself, choose Option 2. If your application writes tuples of the same shape, or event volume is a concern, choose Option 1 and plan to reconcile periodically.

Other entities

The examples above use a user’s app_metadata. Swap the event and the path for other entities: For Option 1, read the same path from input.data.previous_object instead of input.data.object.

Other attributes

Nothing here is specific to metadata. Any field on data.object works the same way, as long as its value maps onto a bounded relation. On a user, for example, a boolean such as blocked or email_verified can drive a relation, and so can an identity-provider field such as the connection a user signed up through. Point the rules at the field’s path instead of app_metadata.*. For a top-level user field, that is input.data.object.<field> and input.data.previous_object.<field>. Check the field’s exact path in the event schema, since nested fields such as identities are arrays and need an iterator rather than a direct path. Don’t use an email address, phone number, or other personally identifiable attribute as the value that ends up in a tuple; see Data privacy. Map it to an opaque value, such as a domain ID, first.

Known limitations

  • Both options handle one field at a time. If several attribute-driven relations change in the same update, each needs its own rule set. For Option 2, that can be its own tuple_filters entry within one rule, scoped to that relation; a single filter can’t reconcile two different relations against two different desired values at once.