How to allow only an array of projects chosen from the Work page in Sanity.io

5 replies
Last updated: Apr 22, 2024
Hi all! I’m having a little trouble, seeing if anyone has some insight here…
I have a work page that will have an array of projects to choose from and sort like so:

export default defineType({
  name: 'work',
  title: 'Work',
  type: 'document',
  singleton: true,
  fields: [
    // ...
    defineField({
      name: 'projects',
      title: 'Projects',
      type: 'array',
      of: [
        {
          type: 'reference',
          to: {
            type: 'project'
          }
        }
      ]
    })
  ]
});
Now, on my home / index page, I’d like to have an array of “featured” projects - but instead of choosing from the general list of “all projects” like on the Work schema above with the
to: { type: 'project' }
, can I somehow allow only an array of projects chosen from the Work page?
Home page looks like this:

export default defineType({
  name: 'index',
  title: 'Home',
  type: 'document',
  singleton: true,
  fields: [
    // ...
    defineField({
      name: 'featuredProjects',
      title: 'Featured Projects',
      type: 'array',
      of: [
        {
          type: 'reference',
          to: {
            type: 'project' // change this to point to the array of work projects from the schema above?
          }
        }
      ]
    })
  ]
});

Let’s say the client has published 10 projects. On the work page they have added 6 projects. Then on the home page, they should only be able to choose from the 6 projects in the work page, and not all 10 projects…
Let me know if this makes sense or if I should elaborate more, ha.

Thanks!!
AI Update

Looking at your schema setup, you want to limit the featuredProjects reference field on your home page to only show projects that have been selected in the projects array on your work page. This is a perfect use case for the dynamic filter option in Sanity reference fields.

You can use the filter option with an async function that queries the work document to get its selected projects, then filters the reference options to only show those. Here's how to implement it:

export default defineType({
  name: 'index',
  title: 'Home',
  type: 'document',
  singleton: true,
  fields: [
    // ...
    defineField({
      name: 'featuredProjects',
      title: 'Featured Projects',
      type: 'array',
      of: [
        {
          type: 'reference',
          to: [{type: 'project'}],
          options: {
            filter: async ({getClient}) => {
              const client = getClient({apiVersion: '2024-01-01'})
              
              // Fetch the work document and get its project references
              const workDoc = await client.fetch(
                `*[_type == "work"][0].projects[]._ref`
              )
              
              // If no projects are selected in work page, show none
              if (!workDoc || workDoc.length === 0) {
                return {
                  filter: '_id == $impossibleId',
                  params: {impossibleId: 'none'}
                }
              }
              
              // Only show projects that are in the work page's projects array
              return {
                filter: '_id in $projectIds',
                params: {projectIds: workDoc}
              }
            }
          }
        }
      ]
    })
  ]
});

This solution:

  1. Uses an async filter function that fetches the work document
  2. Extracts the _ref values from the work page's projects array
  3. Returns a GROQ filter that only shows projects whose _id is in that list
  4. Handles the edge case where no projects are selected on the work page

Important notes:

  • The filter constrains what's shown in the reference picker, but doesn't validate that references stay valid if you later remove projects from the work page
  • You might want to add validation to ensure featured projects are always in the work projects list
  • The filter runs each time you open the reference picker, so it will always reflect the current state of the work page

If you want even tighter integration, you could also add a custom validation rule to ensure featured projects are always in the work projects list:

validation: Rule => Rule.custom(async (featuredProjects, context) => {
  if (!featuredProjects?.length) return true
  
  const {getClient} = context
  const client = getClient({apiVersion: '2024-01-01'})
  const workProjects = await client.fetch(
    `*[_type == "work"][0].projects[]._ref`
  )
  
  const invalid = featuredProjects.filter(
    fp => !workProjects.includes(fp._ref)
  )
  
  return invalid.length === 0 
    ? true 
    : 'Featured projects must be selected in the Work page first'
})

This ensures data integrity even if someone removes a project from the work page after it's been featured.

Yes, you can do this with a
filter
on your reference field. Something like:
{
      name: 'featuredProjects',
      type: 'reference',
      to: [{ type: 'project' }],
      options: {
        filter: async ({ getClient }) => {
          const client = getClient({ apiVersion: '2024-04-19' });

          const referencedProjectIds = await client.fetch(
            `*[_type == 'project' && count(*[_type == 'work' && references(^._id)]) > 0]._id`
          );

          return {
            filter: '_id in $referencedProjectIds',
            params: { referencedProjectIds },
          };
        },
      },
    },
  
Hi
user M
, thanks for the quick reply and great example! I just tried this out, it is kind of working…• I can choose from the projects on the Work page (nice!), but I can only select one project. There is no option to add more, do I need to change
type: 'reference'
to
'array'
?• One other small quirk - when this featuredProjects field is empty, there is a “+ Create” button - any way t hide this? Not trying to create a new project from here…
Thanks again!
Update! 😎 I got this working with the following:
{
  name: 'featuredProjects',
  title: 'Featured Projects',
  type: 'array',
  of: [
    {
      type: 'reference',
      to: [{ type: 'project' }],
      options: {
        ... // same as code above
      }
    }
  ]
}
I think the “+ Create” button is a different topic entirely, I’ll keep Googlin’ this, if you have any ideas or links you could point me towards, that’d be awesome!

Thanks again, problem solved!
Annnnd another victory! In my sanity.config file, I added this document object that hides the Create button on my list of projects on the home page.

export default defineConfig({
  ...
  document: {
    newDocumentOptions: (prev, { currentUser, creationContext }) => {
      if (creationContext.type === 'document' && creationContext.schemaType === 'index') {
        return [];
      }
      return prev;
    }
  }
}
No more questions, all problems solved.
👍
Awesome! Glad you got it sorted!

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?