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)
}
},
@jesschow @jacobsfletch do you all think this is worth a Github issue?
Hey @gregwhitworth it 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
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;
}
}
},
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
I"m using it at the collection level, and I believe it should work at the field level too.
That's what I thought, but for some reason it's not working.
do you get any error messages or can you console log it?
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
Good Morning. The documentation, unlike the collection level, doesn't mention that you can use a function at the field level.
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"),
}
...
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.
That's true, but @jacobsfletch said this
https://discord.com/channels/967097582721572934/1124822503659937812/1125824625318838352Just throwing this in here, might give you some ideas
We created some helper functions to cleanly define who can see/access collections.
Yes, it seems like this is the workflow to use. Cheers
@artmelkon how are you getting access to { user } at the collection level as it's throwing a type error for me.
Are you using {req: {user}}? If so, use only {user}
@Eustachio I've tried both
Can you try it like this? This works for me @gregwhitworth
like what?
if you posted an image it doesn't seem to come through in a reply :/
hidden: ({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
Thanks @Eustachio but 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
Can you console the user object to see what you are getting?
sure @Eustachio
{email: '<myemailaddress>', id: '647bd27c803c975ea9f20c31', collection: 'users', iat: 1690662787, exp: 1690669987}
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
I know, that's what I said above
and showed that it is saved to JWT
https://discord.com/channels/967097582721572934/1124822503659937812/1134944275663179826
So, you can add the roles to the user collection as a relationTo field (if it's not there already)
this is the user collection
I linked to the definition above, saveToJWT is set to true
thus my confusion :/
Yes, I saw that. For what I understand, the user collection has to be Auth enabled.
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;
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
The users collection has the roles field
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
// 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
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)
@Eustachio 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
Cheers. Yes, it was working fine for me before as well.
@Eustachio 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?
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.
@gregwhitworth 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
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?
Can you please show your code? Is this Payload v2 or v3?
This work for me:
admin: {
useAsTitle: "title",
hidden: ({ user }) => !user?.roles?.toString().includes("admin"),
},
Star
Discord
online
Get dedicated engineering support directly from the Payload team..