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.

How to filter out certain data from a collection page?

default discord avatar
Deleted User2 years ago
35

Let's say I have a products collection, and there is a relation to the users collection, where a user has many products. How can I hide products that aren't related to that specific user? I couldn't find a "filter" property. Access control is not what I want. If there are 10 rows of data, and 3 belongs to X user, I want the products page to only list those 3 items. I'm looking for the relevant config, I can handle the logic myself. Thanks. 👍

  • default discord avatar
    markatomniux2 years ago

    Access controls probably is what you want. You can create incredibly complex nested queries with the access control to filter on certain nested elements. What do your collections look like?

  • default discord avatar
    Deleted User2 years ago

    My collections are:


    - Business


    - User


    - Product



    User is default Payload user. Each user belongs to a business, and each product belongs to a business. When the user visits the products page, I want them to only be able to see their own related business' products.



    I figured AC is only for allowing/rejecting operations, not filtering data to show?

  • default discord avatar
    markatomniux2 years ago

    Ok sure, so in your access control view function, you can get the user from the request object. Req.user



    It’s used for both, if you think about from a multi-tenant perspective, we may want to hide information from different orgs, so with Access Control, we filter to only get information relevant to that tenant

  • default discord avatar
    Deleted User2 years ago

    Which AC operation?



    admin?: (args?: any) => Promise<boolean> | boolean;
    create?: Access;
    delete?: Access;
    read?: Access;
    readVersions?: Access;
    unlock?: Access;
    update?: Access;
  • default discord avatar
    markatomniux2 years ago

    Read

  • default discord avatar
    Deleted User2 years ago

    Is there a context or similar argument that I can use to get the user's related business ID?

  • default discord avatar
    markatomniux2 years ago

    And the result of Access Control can either be a Boolean, or it can be a query -

    https://payloadcms.com/docs/queries/overview
  • default discord avatar
    Deleted User2 years ago

    Oh, a query would do.

  • default discord avatar
    markatomniux2 years ago

    You have access to the entire User object, so if you have the org Id saved to the user, you can query on it

  • default discord avatar
    Deleted User2 years ago

    Let me try that, thank you very much

    @191776538205618177

    . 👍

  • default discord avatar
    markatomniux2 years ago

    Or you can use the payload.find() method to do a full DB query. It’s very powerful 😄

  • default discord avatar
    Deleted User2 years ago

    Which do you recommend?

  • default discord avatar
    markatomniux2 years ago

    Returning a query object is better as it is less code. But it is doing pretty much the same thing as payload.find()

  • default discord avatar
    Deleted User2 years ago

    This is interesting, this query only shows the user their own related business in the businesses page. It seems to work, but why? User's ID isn't the business' ID, I'm assuming?


    // collections/Business.ts
    
    read: ({ req }) => {
        return {
            id: {
                equals: req.user.id,
            },
        };
    },


    Hmm, it probably works because there's currently only 1 user and 1 business, so both have the same ID of 1.



    The data and id arguments are undefined. 🤔

  • default discord avatar
    markatomniux2 years ago

    That is strange, that doesn’t look like it should work



    Can you breakpoint debug? Or better yet, throw it in a payload.find() and see what the result is

  • default discord avatar
    Deleted User2 years ago

    It's because I only have 1 user and 1 business and IDs start from 1, so technically it will return 1 result, it's a malformed query. 😄



    Sure.





    I was trying to find what column the businesses table references the users table by.



    Hmm, I guess Payload creates dedicated tables for relations.



    (await req.payload.find({ collection: "businesses" })).docs;
    (await req.payload.find({ collection: "users" })).docs;
    
    [
      {
        id: 1,
        Brand: 'foobar',
        User: [ [Object] ],
        updatedAt: '2024-03-21T21:55:19.623Z',
        createdAt: '2024-03-21T21:55:19.623Z',
        email: 'admin@app.com',
        password: undefined,
        loginAttempts: 0
      }
    ]
    [
      {
        id: 1,
        Type: 'admin',
        updatedAt: '2024-03-23T09:01:03.515Z',
        createdAt: '2024-03-17T18:04:08.155Z',
        email: 'admin@app.com',
        password: undefined,
        loginAttempts: 0
      }
    ]


    They

    are

    related, I just can't see how from the returned data except the ID but that's just because primary keys start from 1.

    :thinking:
  • default discord avatar
    markatomniux2 years ago

    Oh you’re using Postgres’s

  • default discord avatar
    Deleted User2 years ago

    Oh, wait, so "email" is the relation?



    The businesses page shows that the User field is the user's email.

  • default discord avatar
    markatomniux2 years ago

    No I’d is the relation. Thing is though I don’t know of Postgres treats any of this differently

  • default discord avatar
    Deleted User2 years ago

    Yeah.

  • default discord avatar
    markatomniux2 years ago

    Hmm this is a bit of a strange one. I’ve only ever used MongoDB, so I don’t know if some kind of joining table is being created on the Postgres side. Let me check

  • default discord avatar
    Deleted User2 years ago

    I was going to try:


    return {
        User: {
            id: {
                equals: req.user.id
            },
        },
    }

    or users instead of User, but the return type is not expected to be like that, it expects propert -> equals/some other filter -> property -> and so on 🤔



    Since a business row has a User array like:


      {
        id: 1,
        Brand: 'foobar',
        User: [ [Object] ],
        updatedAt: '2024-03-21T21:55:19.623Z',
        createdAt: '2024-03-21T21:55:19.623Z',
        email: 'admin@app.com',
        password: undefined,
        loginAttempts: 0
      }
  • default discord avatar
    markatomniux2 years ago

    Can you drop your collection files here

  • default discord avatar
    Deleted User2 years ago

    Sure.



    import { CollectionConfig } from "payload/types";
    import { config, permissions } from "./config";
    import payload from "payload";
    
    const Business: CollectionConfig = {
        slug: "businesses",
        auth: true,
        fields: [
            {
                name: "Brand",
                type: "text",
                required: true,
            },
            {
                name: "User",
                type: "relationship",
                hasMany: true,
                relationTo: "users",
            },
            {
                name: "Product",
                type: "relationship",
                hasMany: true,
                relationTo: "products",
                hidden: true,
            },
        ],
        hooks: {},
        access: {
            create: permissions.allow.admin,
            read: ({ req: { user } }) => {
                return {
                    "users.id": {
                        equals: user.id,
                    },
                };
            },
            update: permissions.allow.admin,
            delete: permissions.allow.admin,
        },
        admin: {
            ...config.admin,
            description: undefined,
        },
    };
    
    export { Business };


    import { CollectionConfig } from "payload/types";
    import { config } from "./config";
    
    const User: CollectionConfig = {
        slug: "users",
        auth: true,
        admin: {
            ...config.admin,
            description: undefined,
            useAsTitle: "email",
        },
        fields: [
            // Email added by default
            // Add more fields as needed
            {
                name: "Type",
                type: "select",
                options: [
                    {
                        label: "Admin",
                        value: "admin",
                    },
                    {
                        label: "User",
                        value: "user",
                    },
                ],
            },
            {
                name: "Business",
                type: "relationship",
                hasMany: false,
                relationTo: "businesses",
            },
        ],
        hooks: {
            beforeOperation: [
                // to do: don't let creating a second admin user
                async ({ args }) => {
                    return args;
                },
                // to do: don't let deleting the last admin user
                async ({ operation, args }) => {
                    if (operation === "delete" && args.type === "admin") {
                        throw new Error("Not allowed to delete admin.");
                    }
                },
            ],
        },
        access: {},
    };
    
    export { User };


    import { CollectionConfig } from "payload/types";
    import { config } from "./config";
    
    const Product: CollectionConfig = {
        slug: "products",
        fields: [
            {
                name: "Business",
                type: "relationship",
                hasMany: false,
                relationTo: "businesses",
            },
        ],
        admin: {
            ...config.admin,
            description: undefined,
        },
        hooks: {},
        access: {},
    };
    
    export { Product };


    I think I found the solution.



    read: ({ req: { user } }) => {
        return {
            "User.id": {
                equals: user.id,
            },
        };
    },
  • default discord avatar
    markatomniux2 years ago

    has many



    you need an in: [] query

  • default discord avatar
    Deleted User2 years ago

    Hmm, so my query would only show the first related result?



    What is this kind of query style called? GraphQL?



    I should learn it a bit.

  • default discord avatar
    markatomniux2 years ago

    no no, it's because your business collection has a relationship to users, but because that is an array, you can't say users === 'userId'. You'd need to say 'userId' in users



    I believe so

  • default discord avatar
    Deleted User2 years ago

    So this would work?


    "User.id": {
        in: req.user.id,
    },
  • default discord avatar
    markatomniux2 years ago

    I think just "User" should be enough



    because user is an ID, not an object when querying



    unless postgress handles it differently

  • default discord avatar
    Deleted User2 years ago

    I tried that just now, it shows results regardless of in vs not_in.

  • default discord avatar
    markatomniux2 years ago

    What about User.id

  • default discord avatar
    Deleted User2 years ago

    Whereas User.id works only when I use the "in" filter.

  • default discord avatar
    markatomniux2 years ago

    ok that'll be it then

  • default discord avatar
    Deleted User2 years ago

    Thanks a lot again for the trouble

    @191776538205618177

    I appreciate it 👍

  • default discord avatar
    markatomniux2 years ago

    That's ok! glad we got it sorted 🙂

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.