I am wanting to both hide fields as well as alter readonly capabilities in the admin section of a field. I can't seem to get checkRole to work with these fields that expect bools. Here is my code:
admin: {
readOnly: Boolean(({ req: { user } }) => checkRole(['guest'], user)),
hidden: Boolean(({ req: { user } }) => checkRole(['guest'], user))
},
Thanks in advance.
So what I ended up doing was leveraging was
condition
which works but doesn't seem like the path that I would like to take as there will be scenarios in which I do want them visibly rendered but readonly for certain roles. This gets me around this one usecase for the timebeing. So I'd still like help on the above but if someone wants to completely hide a field from a user based on the role here is how to do that:
admin: {
condition: ({user}) => {
return !checkRole(['admin'], user)
}
},
@808734492645785600
do you all think this is worth a Github issue?
how are you getting access to { user } at the collection level as it's throwing a type error for me.
I've tried both
like what?
if you posted an image it doesn't seem to come through in a reply :/
Thanks
@962537363983196240but I am truly baffled. The one on the right has no role, the one on the left has the role of admin and they both get the same result. So I decided to go oldschool and console.log the output and sure enough if I add this:
console.log('Is admin: '+ (user as any).roles?.includes('admin'))
It results in:
Is admin: undefined
Any ideas as to how it could be undefined?
Sure enough, looking at the object it results in an object with no roles. So now I'm going to look to see how this is getting removed.
looking at the field, it is included in the JWT:
name: 'roles',
type: 'select',
// Save to JWT so it is passed to the user object in admin hidden fields (indirectly from useAuth hook)
saveToJWT: true,
hasMany: true,
options: [
{
label: 'Admin',
value: 'admin',
},
],
I would like to note that mongodb and the field itself is filled in with the role of admin if they are one
sure
@962537363983196240
{email: '<myemailaddress>', id: '647bd27c803c975ea9f20c31', collection: 'users', iat: 1690662787, exp: 1690669987}
I know, that's what I said above
and showed that it is saved to JWT
this is the user collection
I linked to the definition above, saveToJWT is set to true
thus my confusion :/
it is
const Users: CollectionConfig = {
slug: 'users',
admin: {
useAsTitle: 'name',
defaultColumns: ['name', 'email'],
hidden: ({user}) => {
console.log(user);
return !(user as any)?.roles?.includes('admin')
}
},
access: {
read: isAdminOrSelf,
create: anyone,
update: isAdminOrSelf,
delete: admins,
admin: ({ req: { user } }) => {
console.log('Access object: ' + user)
return checkRole(['admin', 'supplier'], user);
}
},
hooks: {
afterChange: [loginAfterCreate],
},
auth: true,
fields: UserFields,
timestamps: true,
};
export default Users;
The users collection has the roles field
// import payload from 'payload';
import type { CollectionConfig, FieldHook } from 'payload/types';
import { admins } from '../../access/admins';
import { anyone } from '../../access/anyone';
import { isAdminOrSelf } from '../../access/isAdminOrSelf';
import { slugField } from '../../fields/slug';
import hideCollectionFor from '../../utilities/hideCollectionFor';
import { checkRole } from './checkRole';
import { loginAfterCreate } from './hooks/loginAfterCreate';
const checkFirstUser: FieldHook = async ({ siblingData, data, value, req }) => {
const users = await req.payload.find({ collection: 'users' });
if (users.totalDocs > 0) return data?.role || value;
if (users.totalDocs === 0) {
siblingData.roles = ['admin'];
}
};
const UserFields: CollectionConfig['fields'] = [
{
type: 'tabs',
tabs: [
{
label: 'Account',
fields: [
{
name: 'name',
type: 'text',
},
{
name: 'roles',
type: 'select',
// Save to JWT so it is passed to the user object in admin hidden fields (indirectly from useAuth hook)
saveToJWT: true,
hasMany: true,
options: [
{
label: 'Admin',
value: 'admin',
},
],
hooks: {
// beforeChange: [checkFirstUser, protectRolesBeforeCreate],
beforeChange: [checkFirstUser],
},
access: {
create: admins,
update: admins,
},
},
]
},
]
}
];
const Users: CollectionConfig = {
slug: 'users',
admin: {
useAsTitle: 'name',
defaultColumns: ['name', 'email'],
hidden: ({ user }) => hideCollectionFor(user, ['supplier', 'customer', 'host']),
},
access: {
read: isAdminOrSelf,
create: anyone,
update: isAdminOrSelf,
delete: admins,
admin: ({ req: { user } }) => checkRole(['admin', 'supplier'], user),
},
hooks: {
afterChange: [loginAfterCreate],
},
auth: true,
fields: UserFields,
timestamps: true,
};
export default Users;
To simplify this I've removed a number of fields that we track for the users but this is my user collection in general
That's while you'll only see one tab, whereas we actually have 3 for profile photos, orders, etc
actively working in another branch but this would make sense since this
wasworking at some point and then broke. I'll file a bug once I confirm this works as expected
alas that did not fix it. I completely removed the tabs, moved all of the fields directly into the fields area, etc to no avail. Are you working on a next project or directly with payload alone?
Hey
@612318753635565792it looks like you just you need to use functions for your
readOnly
and
hidden
properties, right now you have it defined as a constant, i.e. change
Boolean()
to
() => Boolean()
I want to hide a field based on the user role too, but I can't make it work. There is a type error. I have tried adding the boolean type but it doesn't work
I tried this
Trying hiding the field using condition, I was having an error, so I wrote some console.log to see what user prints, and it prints undefined. The user data works well i.e when it's called in defaultValue property
Hi, thanks for helping. One question, Is that admin property at the collection level or field level? The code I posted works fine to hide a collection based on the user role but it doesn't work if it's used to hide a field. I'll try your solution later when I'm back to work. Cheers
That's what I thought, but for some reason it's not working.
Yes, I get a type error, and I haven't been able to console.log it because of the type error
For example, in your code if you console.log(user) you'll see the user data, but if you use the same code in a hidden property at a field level it doesn't work. Maybe I'm missing something
This works for me too thanks. Have you managed to make use of the hidden property? I haven't been able to, I get type errors.
Another option I tried and it worked for me was to use the access property at field level
...
access: {
// To hide a field using access at a field level
read: ({req: { user } }) => user.roles.includes("admin"), // only admin user will read
// To make a field read only
create: create: ({req: { user } }) => user.roles.includes("admin"), // only admin users can create and update
update: ({req: { user } }) => user.roles.includes("admin"),
}
...
That's true, but
@808734492645785600said this
https://discord.com/channels/967097582721572934/1124822503659937812/1125824625318838352Yes, it seems like this is the workflow to use. Cheers
Are you using {req: {user}}? If so, use only {user}
Can you try it like this? This works for me
@612318753635565792hidden: ({user}) => (user as any).roles.includes('admin')
This should work at the collection level
Sorry I'm typing on my phone. This will return true when the user role is admin, add ! at the beginning to make it return false
Can you console the user object to see what you are getting?
So the roles are not present, that's why it throws undefined.
Is the role field in the users collection?
I cant see you're saving the roles field to JWT
So, you can add the roles to the user collection as a relationTo field (if it's not there already)
Yes, I saw that. For what I understand, the user collection has to be Auth enabled.
I will be back at home soon, I'll check my user collection and share it with you.
Ok ok. In which collection you have the roles field?
I can't see any fields on your users collection
this is my users collection setting
export const Users: CollectionConfig = {
slug: "users",
auth: true,
admin: {
useAsTitle: "email",
},
access: {
// Admins can read all, but any other logged in user can only read themselves
read: isAdminOrSelf,
// only admins can create users
create: isAdmin,
// Admins can update all, but any other logged in user can only update themselves
update: isAdminOrSelf,
// only admins can delete
delete: isAdmin,
admin: isAdminOrSiteUser,
},
fields: [
{
type: "row",
fields: [
{
name: "firstName",
type: "text",
required: true,
},
{
name: "lastName",
type: "text",
required: true,
},
],
},
{
name: "roles",
// Save this field to JWT so we can use from `req.user`
saveToJWT: true,
type: "select",
hasMany: true,
defaultValue: ["editor"],
access: {
// only admins can create or update a value for this field
create: isAdminFieldLevel,
update: isAdminFieldLevel,
},
options: [
{
label: "Admin",
value: "admin",
},
{
label: "Editor",
value: "editor",
},
],
},
],
};
this is what I get from the console
I think you need to bring your UserFields inside the User collection
From your previous console log of the user object, none of the fields in the UserFields have been registered, I think you might have to add this in your User collection: (I don't guarantee this is the solution)
...
timestamps: true,
fields: UserFields,
...
Apologies, I missed that you already did this, but I think these fields are not being registered correctly within the User collection
here it is
add the saveToJWT to the top level in to the tabs field as roles is nested within, I replicated your setup and it works
edit: this doesn't apply, see below explanation
I think there's a bug with Payload, and I think there's nothing wrong with your code. If you login and you console.log the user object without refreshing the browser, the roles array won't come up, but if you refresh the browser the roles array will show. Screenshot without a refresh:
no _strategy: "local-jwt"
Now, screenshot after a browser refresh, the roles array is there as well as _strategy: "local-jwt"
I have had a similar issue reading a value from a nested field within the user object.
https://discord.com/channels/967097582721572934/1133806906968907806I think this is happening since one of the latest updates. But, I might be wrong and it's possible that it's something I'm missing 🤔
This works (see screenshot). Moving the roles field to a top level position instead of being nested within a tab field.
Reading again the docs, the field to be savedToJWT has to be top level, so it seems like it can't be nested within other fields. Pls, someone corrects me if I'm wrong... (so, maybe there's not a bug and this is the normal behaviour)
Cheers. Yes, it was working fine for me before as well.
Ah, I work with payload alone. Have you checked that when you refresh the browser if the jwt fields come up?
Hi, I did a test today, I console.log the user object from the hidden property (which runs on the client )at a collection level and another console.log for the user from the access property (which runs on the server). The test is done on first load or after login without any page refresh. Please see screenshot
Edit: As you can see in the screenshot the user data on the client doesn't show the roles field. I used the roles field nested back within a tab field as per your original code for this test.
The data from the server is correct. I guess that it's better to hide a collection using access control as the data is more accurate.
I'll be interested to see if you run a console.log on the server if you get the roles field. Screenshot shows how I run the test
Can you please show your code? Is this Payload v2 or v3?
This is the way I've implemented hiding fields in a collection based on user roles using conditions.
This is on Payload V2 (I haven't upgraded yet)
1.I created a file for a
beforeRead
collection hook
SetUserRole.ts
import {
CollectionBeforeReadHook,
} from "payload/types";
// before read hook
export const SetUserRoleHook: CollectionBeforeReadHook = ({
doc,
req: { user },
}) => {
doc.userRole = user?.roles;
return doc;
};
Within the collection file:
2.Create a field call
userRole
theCollectionFile.ts
// collection hooks
...
hooks: {
beforeRead: [SetUserRoleHook] // import the file for this hook
}
...
fields: [
... other fields
// user role field - hidden
// this field will be always reading the user role and add it as its value
// you can also use a field hook to update the user role value instead of using a collection hook
{
name: "userRole",
type: "text",
hidden: true,
defaultValue: ({ user }) => {
return user?.roles;
},
},
// a field you'd like to hide based on user roles
// in this example the field private field will be hidden to those users with the role "user" or "editor"
{
name: "privateField"
type: "text",
admin: {
condition: (data) => {
if (
data?.userRole === "user" ||
data?.userRole === "editor"
) {
return false;
}
return true;
},
},
}
]
...
I might be wrong, but condition only accepts
data
object as props, there isn't a way to read the
user
info. So my approach was to create a field within the collection to have the user role as a value, as this value will then be available in the
data
object
I hope this is helpful!
Is this on V3?
Thanks, I should be upgrading to V3 soon. Cheers!!!
I think that when you hide a field using admin, the value is also unavailable. I needed the field to be hidden but the value to be available for operations in the background.
Yes, that's what I meant, that field is still visible in the api despite being hidden. It might have changed in V3 that this is possible, I'm still in V2. In V2 there was a type error somewhere, I don't remember, this is an old post. I will try what you did in my code. Cheers
I have tried your solution in V2 and still doesn't work. The main reason is that the user data doesn't seem to be read as prop, so I can't perform any operation based on it. I need to upgrade to V3. Cheers
This is how solved the problem. The function will return true when the role is other than 'admin'. The image is hard to read. admin: {
useAsTitle: 'title',
hidden: ({ user }) => {
if (!user.roles.toString().includes('admin')) {
return true;
}
}
},
I"m using it at the collection level, and I believe it should work at the field level too.
do you get any error messages or can you console log it?
Good Morning. The documentation, unlike the collection level, doesn't mention that you can use a function at the field level.
The only time I use hidden is when I don't want users to see some collections on the dashboard. I use access at the field level revoking the user's ability to edit some specific fields.
Just throwing this in here, might give you some ideas
We created some helper functions to cleanly define who can see/access collections.
Hello, sorry for opening up this old topic but did anyone manage to get the initial request to work? I'm talking about how to hide one field (column) of a collection using the hidden property with a function.
You can try this
admin: {
useAsTitle: 'title',
hidden: ({ user }) => {
// Hide the item for editors and members
return (user.roles as string[]).some(role => ['editor', 'members'].includes(role))
},
},
Why is my
user
object
undefined
within the hidden function on the field level?
This work for me:
admin: {
useAsTitle: "title",
hidden: ({ user }) => !user?.roles?.toString().includes("admin"),
},
Hi can't make it work for fields
Found an easier solution. Saved roles in jwt. And in the field :
admin: {
position: 'sidebar',
hidden: Boolean(({ user }: { user: { roles?: string[] } }) => user?.roles?.includes('editor')),
},
yes
the field is just hidden. I think it will not change the rest. Don't know what you mean by operations, the field will still be available in the api call
I tested it again, and it indeed doesn't work on v3 either. My mistake. I added a relational user field, and I think we can use conditions in the other fields with siblingData. I'm trying to figure out the easiest solution too
I got only the user id instead of the object. User is in data.user for me
Not quite sure about v2 but you can access the user in the admin>condition. That's how I hide my fields while retaining the access.
Not sure what you mean but I'm accessing the user property inside the 3rd argument. See example field below where I hide the field using the admin condition property.
{
label: 'Global Roles',
name: 'roles',
type: 'relationship',
relationTo: 'roles',
hasMany: true,
required: true,
admin: {
condition: (_data, _siblingData, { user }) => Boolean(hasSuperAdminRole(user?.roles)), // hide this field if user doesn't have super admin role
disableListColumn: true,
},
},
Star
Discord
online
Get dedicated engineering support directly from the Payload team.