Permission Sets
Event Streams (in early access)A permission set is the unit of precomputed authorization data that Materialize produces. Where SpiceDB answers a permission question on demand by walking the relationship graph, Materialize continuously hydrates the membership of the permissions you configure it to watch and exposes that denormalized data to your application.
Conceptually, a permission set captures two kinds of edges:
- Member → set — a subject (e.g.
user:evan) is a member of a permission set (e.g.document:123#view). - Set → set — one set is nested inside another. The nested set can be a plain relation, like a group membership (
group:engineering#membergrantsdocument:456#view), or another permission entirely, like an edit permission that also grants view access (document:123#editgrantsdocument:123#view).
See the schema and relationships that produce the edges above
definition user {}
definition group {
relation member: user
}
definition document {
relation viewer: user | group#member
relation editor: user
permission edit = editor
permission view = viewer + edit
}document:123#viewer@user:evan
document:123#editor@user:alex
document:456#viewer@group:engineering#memberdocument:123#viewer@user:evanmakesuser:evana member of thedocument:123#viewpermission set.document:123#editor@user:alexmakesuser:alexa member ofdocument:123#edit. Becauseviewis defined asviewer + edit,document:123#editis nested indocument:123#view— the set → set edge from the permission example above.document:456#viewer@group:engineering#membermakes thegroup:engineering#memberrelation itself a viewer ofdocument:456, nestinggroup:engineering#memberinsidedocument:456#view— the set → set edge from the group example above.
Materialize (and the files it produces, see DownloadPermissionSets) calls the nested set the child and the set it grants into the parent — but this isn’t a containment hierarchy like a filesystem or an org chart, where a parent owns and cascades down to its children. It’s closer to set membership: being in the child set implies you’re also in the parent set, so the grant flows from child up to parent, not parent down to child. It’s also not a strict tree — a single child set can have many parents at once (e.g., one group’s membership might grant view access to many different documents), so don’t assume a child has only one parent.
Together these let your application reconstruct “which resources can this subject access” — and the inverse — without issuing a LookupResources or CheckPermission call per request.
This is what makes authorization-aware search, sorting, and filtering over large result sets practical.
How you obtain permission sets
Permission sets reach your application through two complementary APIs:
- LookupPermissionSets — reads the current permission sets as an initial backfill. See The permission set lifecycle.
- WatchPermissionSets — streams changes to permission sets as relationships and schema evolve. See Snapshots for how the two stay consistent.
Storing permission sets
The shape of the data maps cleanly onto relational tables (a member_to_set and a set_to_set table is the most flexible model), or into a secondary index such as Elasticsearch for ACL-filtered search.
See Syncing to a Relational Database for worked examples.
The fields returned by LookupPermissionSets and WatchPermissionSets are identical — you store
them the same way regardless of which API delivered them. That symmetry is what lets you backfill
with one API and keep current with the other.