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
gregwhitworth4 weeks ago
10

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?



@839277522956845158

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



@962537363983196240

I've tried both







like what?



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



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



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



https://discord.com/channels/967097582721572934/1124822503659937812/1134944275663179826

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



@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



@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?

  • 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



    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

    @808734492645785600

    said this

    https://discord.com/channels/967097582721572934/1124822503659937812/1125824625318838352

    Yes, 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

    @612318753635565792

    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



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



    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.



    @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



    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

  • 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;


    }


    }


    },



    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.

  • 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
    puetzz11 months ago

    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
    deslovestoparty7 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
    simka09975 months ago

    Why is my

    user

    object

    undefined

    within the hidden function on the field level?

  • default discord avatar
    rxtsel4 months ago

    This work for me:


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


  • default discord avatar
    nabil7172last month

    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

  • default discord avatar
    2me2you4 weeks 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

    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 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.