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.

Filter relationship by another relationship

default discord avatar
arctomachinelast year
4

Options collection has tags.


Inside block I want to select only options that have particular tag.


What is correct approach here?



Would be great if filter can be applied without saving document first.



Currently I have this. I tried to use filterOptions, but got confused by syntax and still got all items regardless


// options collection
{
    name: 'tags',
    type: 'relationship',
    relationTo: 'tags',
    hasMany: true,
},

// tags collection
{
    name: 'name',
    type: 'text',
    required: true,
},

// block that needs to select options with chosen tags
{
    name: 'tags',
    type: 'relationship',
    relationTo: 'tags', // options document needs to have this tag
    hasMany: true,
},
{
    name: 'show',
    type: 'relationship',
    relationTo: 'options', // to appear in this list
    hasMany: true,
},
  • default discord avatar
    natjb_74591last year

    Hi happy to try to help. If I understand your challenge correctly, you want a collection (that's not options or tags) to be able to select tags and as a result have option selections filtered by those tags?



    If so, try something like this (note I haven't tested this so it's bound to need some debugging!). Hope it helps.



    Other Collection:
    {
      name: 'tags',
      label: 'Tags',
      type: 'relationship',
      relationTo: 'tags',
      hasMany: true,
    },
    {
      name: 'options',
      label: 'Options',
      type: 'relationship',
      relationTo: 'options',
      filterOptions: async ({ relationTo, data }): Promise<Where> => {
        if(relationTo === 'options') {
          if (data?.tags?.relationTo === 'tags' && data?.tags?.value) {
            try {
              // You'll need to get all options for the subset of tags
              // not sure if there's a better way but this should work
              const payload = await getPayload({ config: configPromise })
              const result = await payload.find({
                pagination: false,
                collection: 'options' as CollectionSlug,
                where: {
                  'tags.value': { equals: data.tags.value },
                },
                depth: 0,
              })
              // Return an array of IDs from the result documents
              const optionValues = result?.docs.map(doc => doc.id) ?? []
              return {
                options: {
                  equals: {
                    relationTo: 'options',
                    value: { id: { in: optionValues } },
                  },
                },
              }
            } catch (error) {
              console.error('Error getting options:', error)
              return {}
            }
          }
        }
        return {}
      },
      hasMany: true,
      admin: {
        allowCreate: false,
        allowEdit: false,
      }
    },
  • default discord avatar
    arctomachinelast year

    Like this I get all options, with or without tag

  • default discord avatar
    natjb_74591last year

    RE: Getting all options, you may need to add a test that checks if no tags are selected and return nothing:


    if (!data?.tags?.value) {


    // Return a where clause that will result in no matches


    return {


    id: {


    in: []


    }


    }


    }



    Actually a good idea to add that as default and as the result of the error in try/catch as well. Double checking my logic for the filter itself now.



    Apologies, as mentioned, I can't test this at the moment - try this - adjusted to simplify, query the correct collection (i.e. tags 🤦‍♀️ ) and also refactored for tags likely being an array due to hasMany. Hopefully this is closer! Can you share any console errors if it doesn't work?



    filterOptions: async ({ relationTo, data }): Promise<Where> => {
      if(relationTo === 'options') {
        // Check if tags field exists and has values
        if (data?.tags && Array.isArray(data.tags) && data.tags.length > 0) {
          try {
            // First get the selected tags' associated options
            const selectedTags = await payload.find({
              collection: 'tags',
              where: {
                id: {
                  in: data.tags.map(tag => tag.value)
                }
              },
              depth: 0
            });
    
            // Extract all option IDs from the selected tags
            const optionIds = selectedTags.docs.reduce((acc, tag) => {
              if (tag.options && Array.isArray(tag.options)) {
                const tagOptionIds = tag.options.map(opt => opt.value);
                return [...acc, ...tagOptionIds];
              }
              return acc;
            }, []);
    
            return {
              id: {
                in: optionIds
              }
            }
          } catch (error) {
            console.error('Error getting options:', error)
            return {
              id: { in: [] }
            }
          }
        }
        return {
          id: { in: [] }
        }
      }
      return {
        id: { in: [] }
      }
    }
  • default discord avatar
    arctomachinelast year

    I am 99% confident it will not work because options have tags, not tags have options



    No errors, it just shows full lst



    I did this and looks like it works. Is it right though?


    filterOptions: async ({
        relationTo,
        siblingData,
    }): Promise<Where> => {
        if (relationTo !== 'options') {
            return {
                id: { exists: true },
            }
        }
    
        const tagIds = (siblingData as ShowListBlockType).tags?.map((tag) => {
            if (typeof tag === 'string') {
                return tag
            }
    
            return tag.id
        })
    
        const payload = await getPayload({
            config: payloadConfig,
        })
    
        const allOptions = (
            await payload.find({
                collection: 'options',
            })
        ).docs
    
        const filteredOptions = allOptions.filter((option) => {
            const optionTagIds = option.tags
                ?.map((tag) => {
                    if (typeof tag === 'string') {
                        return null
                    }
    
                    return tag.id
                })
                ?.filter((option) => option !== null)
    
            if (!optionTagIds?.length) {
                return false
            }
    
            return optionTagIds.every((optionTagId) =>
                tagIds?.includes(optionTagId),
            )
        })
    
        const optionIds = filteredOptions.map((option) => option.id)
    
        return {
            id: {
                in: optionIds,
            },
        }
    },


    id: { in: [] }

    is bugged, it returns all instead of none

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.