You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Feature Request] Field-level access overrides for implicit relations #2382
Is your feature request related to a problem? Please describe.
In ZenStack v3, implicit relations (e.g. many-to-many) cannot be updated unless the related model allows update. This makes it impossible to safely support common scenarios where a model is intentionally read-only, but users should still be able to create or remove links to it.
Example: Club: full access for owner Activity: publicly readable, no create/update/delete Club ↔ Activity: many-to-many
A club owner should be able to link/unlink existing Activities to their Club, but because Activity does not allow update, relation updates are blocked. The only workaround is to relax model-level permissions (unsafe) or introduce an explicit join table.
Describe the solution you'd like
Support fine-grained authorization for implicit relation updates, without requiring update access on the related model.
This could be achieved by one or more of the following:
Explicit policies for implicit relation updates (connect / disconnect)
Field-level overrides of model access policies (e.g. allow updates on a relation field while the model itself remains read-only)
First-class separation of relation mutation permissions from general update
This would enable safe linking of read-only reference data (tags, activities, categories, etc.) to user-owned models.
Describe alternatives you've considered
Defining an explicit join table and configuring access policies on it: Works, but adds unnecessary schema complexity for a very common use case
Granting update access on the related model: Not acceptable, as it allows unintended mutations of shared data
Additional context
This appears to be a missing feature in v3. Field-level overrides or relation-level access control existed in some form in v2 but are not yet available in v3. This limitation was confirmed by @ymc9, who indicated that explicit join tables are currently the only supported solution.
Following up on my workaround — I analyzed its limits and it's narrower than it looks:
The workaround breaks when two relations share the same endpoint pair with the same update policies. A link's effective permission is the intersection of the UPDATE policies of the two endpoint rows, evaluated per row without relation context. So:
✅ Club.activities (owner-only) vs Event.activities (admin-only) → works, different writable endpoints
❌ Club.activities vs Club.featuredActivities → impossible, same Club row, same policy
❌ Activity <-> Activity (two read-only models) → impossible
In those cases the only option is an explicit join table, which is painful for a very common use case.
I'd be happy to have a concrete API (first-class link/unlink policies on the relation field).
Activity
ludovicmotte commentedon Sep 18, 2026
Hi @Jamie-Fairweather
I had the same issue.
I just found a workaround:
Hope it helps.
But, yes, I hope Zenstack will provide a cleaner solution.
ludovicmotte commentedon Sep 22, 2026
Following up on my workaround — I analyzed its limits and it's narrower than it looks:
The workaround breaks when two relations share the same endpoint pair with the same update policies. A link's effective permission is the intersection of the UPDATE policies of the two endpoint rows, evaluated per row without relation context. So:
✅ Club.activities (owner-only) vs Event.activities (admin-only) → works, different writable endpoints
❌ Club.activities vs Club.featuredActivities → impossible, same Club row, same policy
❌ Activity <-> Activity (two read-only models) → impossible
In those cases the only option is an explicit join table, which is painful for a very common use case.
I'd be happy to have a concrete API (first-class link/unlink policies on the relation field).
ymc9 commentedon Oct 27, 2026
Released in v3.10.0 via #2858.