Please give me some advice from your experience on how it is better to restrict users of the GraphQL API from modifying all fields of a document except for several that are explicitly allowed, and allow them to modify only documents related to the current user?
Let's take, as an example, the Notifications collection that has 10+ fields, and I need to allow users to update only two fields: isRead and archived, and only for the notifications related to them (doc.owner == user.id).
How to implement this in the best way? I see these options:
This option is recommended in the docs, but it looks to me not very good for performance, because the same check will be executed 10+ times (one time for each field).
Looks good to me, but is it okay to analyze the input data here and produce a conditional result (true/false) depending on the input data?
throw new Error() to stop processing.This looks good too, but I don't like that the access check is performed not in the access control hook, but in the operation execution hook.
This looks too complicated for such a common task.
--
So, which approach is better to use for such tasks? Or suggest another option that is better than described.
And would be awesome to update the documentation with more examples for such cases.
Hey @MurzNN this is a great question! I've run into this myself a few times.
My suggested approach would be something like this:
// Collection-level access returns a where constraint for ownership:
access: {
update: ({ req: { user } }) => {
if (!user) return false
if (user.roles?.includes('admin')) return true
// Returns a query constraint — only matches docs where owner === current user
return {
owner: { equals: user.id },
}
},
},
Then you can apply that access to each of your fields:
{ name: 'owner', type: 'relationship', access: { update: adminOnly } },
{ name: 'createdBy', type: 'relationship', access: { update: adminOnly } },
// ... repeat for all restricted fields
Since adminOnly is a single function reference, there's no real boilerplate — you're just listing which fields are restricted. The performance concern about running it 10+ times is negligible: these are synchronous in-memory checks, not DB queries.
Only the ownership constraint at the collection level hits the database, and that only runs once per request.
For read access, you can use the same pattern — one shared adminOnly function on the fields you want to hide from regular users.
The beforeChange hook and custom mutation approaches work but put access control logic in the wrong layer. The where-constraint pattern is the "Payload way" to handle this.
Thank you a lot for the example! My worry is not only about boilerplate, but also about performance: if we have 10 fields with the 'adminOnly' access hook, this function will be executed 10 times just to load a single document. Okay, adminOnly is a pretty lightweight, but if this check needs DB queries, it will be toooo slow.
For example, when reading the Product document and deciding whether we should return hidden fields, we need to check if the user is a member of the Organization that manages this product, so do a query like this:
const isProductOwner = payload.find(
collection: 'organizations'
where {
id: { equals: product.organization )
members: { equals: user.id)
}
)So, it's better to execute this query once on the collection level access hook than 10 times in each field access hook, right? ;)
And, actually, the same question is for the access control function for the "read" operation: I need to allow reading only a limited list of fields: id, createdAt, title, body, isRead, and archived, reading other fields should be restricted.
Creating access control functions for each field would be a lot of boilerplate, and not good for performance, right? It's much better to handle a "allow list" of fields in a single place.
Star
Discord
online
Get dedicated engineering support directly from the Payload team.