Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Hide a field based on a role

default discord avatar
gregwhitworthlast year
58

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) } },

@854377910689202256

@808734492645785600

do you all think this is worth a Github issue?

  • discord user avatar
    jacobsfletch
    last year

    Hey

    @612318753635565792

    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()
  • default discord avatar
    eustachi0last year

    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

  • default discord avatar
    artmelkonlast year

    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;


    }


    }


    },

  • default discord avatar
    eustachi0last year

    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

  • default discord avatar
    artmelkonlast year

    I"m using it at the collection level, and I believe it should work at the field level too.

  • default discord avatar
    eustachi0last year

    That's what I thought, but for some reason it's not working.

  • default discord avatar
    artmelkonlast year

    do you get any error messages or can you console log it?

  • default discord avatar
    eustachi0last year

    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

  • default discord avatar
    artmelkonlast year

    Good Morning. The documentation, unlike the collection level, doesn't mention that you can use a function at the field level.

  • default discord avatar
    eustachi0last year

    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"),
      }
    ...
  • default discord avatar
    artmelkonlast year

    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.

  • default discord avatar
    eustachi0last year
  • default discord avatar
    _sadajo_last year

    Just throwing this in here, might give you some ideas



    https://gist.github.com/sdjnes/b63e025496cdd0961359f6b03f705425

    We created some helper functions to cleanly define who can see/access collections.

  • default discord avatar
    eustachi0last year

    Yes, it seems like this is the workflow to use. Cheers

  • default discord avatar
    gregwhitworthlast year
    @839277522956845158

    how are you getting access to { user } at the collection level as it's throwing a type error for me.

  • default discord avatar
    eustachi0last year

    Are you using {req: {user}}? If so, use only {user}

  • default discord avatar
    gregwhitworthlast year
    @962537363983196240

    I've tried both





  • default discord avatar
    eustachi0last year

    Can you try it like this? This works for me

    @612318753635565792
  • default discord avatar
    gregwhitworthlast year

    like what?



    if you posted an image it doesn't seem to come through in a reply :/

  • default discord avatar
    eustachi0last year

    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

  • default discord avatar
    gregwhitworthlast year

    Thanks

    @962537363983196240

    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

  • default discord avatar
    eustachi0last year

    Can you console the user object to see what you are getting?

  • default discord avatar
    gregwhitworthlast year

    sure

    @962537363983196240

    {email: '<myemailaddress>', id: '647bd27c803c975ea9f20c31', collection: 'users', iat: 1690662787, exp: 1690669987}
  • default discord avatar
    eustachi0last year

    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

  • default discord avatar
    gregwhitworthlast year

    I know, that's what I said above



    and showed that it is saved to JWT



    https://discord.com/channels/967097582721572934/1124822503659937812/1134944275663179826
  • default discord avatar
    eustachi0last year

    So, you can add the roles to the user collection as a relationTo field (if it's not there already)

  • default discord avatar
    gregwhitworthlast year

    this is the user collection

  • default discord avatar
    eustachi0last year
  • default discord avatar
    gregwhitworthlast year

    I linked to the definition above, saveToJWT is set to true



    thus my confusion :/

  • default discord avatar
    eustachi0last year

    Yes, I saw that. For what I understand, the user collection has to be Auth enabled.

  • default discord avatar
    gregwhitworthlast year

    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;
  • default discord avatar
    eustachi0last year

    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

  • default discord avatar
    gregwhitworthlast year

    The users collection has the roles field

  • default discord avatar
    eustachi0last year

    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

  • default discord avatar
    gregwhitworthlast year
    // 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

  • default discord avatar
    eustachi0last year

    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/1133806906968907806

    I 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)

  • default discord avatar
    gregwhitworthlast year
    @962537363983196240

    actively working in another branch but this would make sense since this

    was

    working at some point and then broke. I'll file a bug once I confirm this works as expected

  • default discord avatar
    eustachi0last year

    Cheers. Yes, it was working fine for me before as well.

  • default discord avatar
    gregwhitworthlast year
    @962537363983196240

    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?

  • default discord avatar
    eustachi0last year

    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.



    @612318753635565792

    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

  • default discord avatar
    puetzzlast year

    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.

  • default discord avatar
    deslovestoparty10 months ago

    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)) }, },
  • default discord avatar
    simka09978 months ago

    Why is my

    user

    object

    undefined

    within the hidden function on the field level?

  • default discord avatar
    eustachi08 months ago

    Can you please show your code? Is this Payload v2 or v3?

  • default discord avatar
    rxtsel7 months ago

    This work for me:


    admin: {
      useAsTitle: "title",
      hidden: ({ user }) => !user?.roles?.toString().includes("admin"),
    },


  • default discord avatar
    nabil71724 months ago

    Hi can't make it work for fields

  • default discord avatar
    eustachi04 months ago

    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!

  • default discord avatar
    nabil71724 months ago

    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')),
      },
  • default discord avatar
    eustachi04 months ago

    Is this on V3?

  • default discord avatar
    nabil71724 months ago

    yes

  • default discord avatar
    eustachi04 months ago

    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.

  • default discord avatar
    nabil71724 months ago

    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

  • default discord avatar
    eustachi04 months ago

    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

  • default discord avatar
    nabil71724 months ago

    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

  • default discord avatar
    2me2you4 months ago

    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.



    https://payloadcms.com/docs/admin/fields#conditional-logic
  • default discord avatar
    nabil71724 months ago

    I got only the user id instead of the object. User is in data.user for me

  • default discord avatar
    2me2you4 months ago

    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,
      },
    },
  • default discord avatar
    dankhazipeter3 weeks ago

    Hi all! I want to reopen this issue again 😄 I'm trying to fix it so that a column only appears when the logged in user is admin. The

    condition

    is no good because it only controls the view and edit pages. It does not make the column disappear. The column is disappeared by

    disableListColumn

    , which only takes fixed Boolean. Is there a workaround for this in Payload?

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get dedicated engineering support directly from the Payload team.