Adding a feature flag to a Sanity type and discussing possible solutions.

7 replies
Last updated: Jul 25, 2022
Good morning everyone!
I wanted to add an
isFeatured
flag in the Sanity type.

export default {
    name: 'myType',
    type: 'object',
    title: 'My Type',
    fields: [
       ...
        {
            title: 'Is featured',
            name: 'isFeatured',
            type: 'boolean',
        },
    ],
    initialValue: {
        isFeatured: false,
    },
};
It would be marked
true
for only one object of this
myType
type. Unfortunately, here the validation itself, which will not allow to change this value to
true
when in another document this
isFeatured
flag is already on
true
, is not suitable (user-unfriendly).
I was thinking about to make a switch that - when set to
true
in obj1, would change obj2 (in which previously was `isFeatured`flag set on
true
) to
false
.
I searched the Internet for inspiration. Unfortunately, I didn't find anything that would help me. Do you have any ideas where I could start?
🤗
AI Update

Great question! What you're looking for is a way to ensure only one document has isFeatured: true at a time, and automatically toggle off the previous one when a new one is marked as featured. This is definitely achievable in Sanity!

The best approach is to create a custom input component for your boolean field that checks for other featured items and automatically unsets them when the current one is toggled on. Here's how to implement it:

Custom Input Component Approach

Create a custom input component that uses the Sanity client to patch other documents:

import { useCallback } from 'react'
import { set, unset } from 'sanity'
import { Switch, Stack, Text } from '@sanity/ui'
import { useClient } from 'sanity'

export function FeaturedToggle(props) {
  const { value, onChange } = props
  const client = useClient({ apiVersion: '2024-01-01' })

  const handleToggle = useCallback(async () => {
    if (!value) {
      // User is turning this one ON, so turn off all others
      try {
        // Find all other documents with isFeatured: true
        const query = `*[_type == "myType" && isFeatured == true && _id != $currentId]`
        const otherFeatured = await client.fetch(query, { 
          currentId: props.document._id 
        })

        // Create a transaction to update all at once
        let transaction = client.transaction()
        
        // Unset isFeatured on all other documents
        otherFeatured.forEach(doc => {
          transaction = transaction.patch(doc._id, { unset: ['isFeatured'] })
        })

        // Execute the transaction
        await transaction.commit()

        // Now set this one to true
        onChange(set(true))
      } catch (error) {
        console.error('Error updating featured status:', error)
      }
    } else {
      // User is turning this one OFF
      onChange(unset())
    }
  }, [value, onChange, client, props.document._id])

  return (
    <Stack space={2}>
      <Switch 
        checked={value || false} 
        onChange={handleToggle}
      />
      {value && (
        <Text size={1} muted>
          This is the featured item. Toggling another will unset this one.
        </Text>
      )}
    </Stack>
  )
}

Then in your schema, use this custom component:

export default {
  name: 'myType',
  type: 'object',
  title: 'My Type',
  fields: [
    // ... other fields
    {
      title: 'Is featured',
      name: 'isFeatured',
      type: 'boolean',
      components: {
        input: FeaturedToggle
      }
    },
  ],
  initialValue: {
    isFeatured: false,
  },
}

How It Works

  1. useClient hook - Gets a configured Sanity Client instance to perform API operations
  2. GROQ query - Finds all other documents of the same type with isFeatured: true
  3. Transaction - Uses the Mutation API's transaction support to atomically update multiple documents
  4. Patch operations - Unsets the flag on all other documents before setting it on the current one
  5. onChange with set/unset - In Studio v3, the onChange callback accepts patch operations directly using the set() and unset() functions imported from sanity

The key advantage is that this gives immediate visual feedback to the user and handles the toggle behavior exactly as you described. The transaction ensures all patches happen atomically, maintaining data consistency.

Alternative: Sanity Functions (More Robust)

For a production-ready solution that works even when documents are updated via API or other means (not just through the Studio UI), consider using Sanity Functions to handle this logic server-side. You could create a function that listens to document mutations and automatically updates other documents when one is marked as featured, ensuring the logic runs regardless of how the content is edited.

The custom input component approach is perfect for your use case since it's user-friendly and provides the toggle behavior you're looking for! 🤗

Hi!I would create a singleton document where the client can choose a featured item among all the objects.
{      name: 'featuredPhotos',      title: 'Featured Photos',      type: 'array',      description: 'Displayed on home page. Choose exactly 3 photos.',      of: [        {          type: 'reference',          to: [{ type: 'photo' }],        },      ],      fieldset: 'company',      options: { layout: 'grid' },      validation: (Rule) => Rule.required().min(3).max(3),    },
something like this:
{
  name: 'featuredItem',
  type: 'object',
  fields: [
    {
      title: 'Featured item',
      name: 'item',
      type: 'reference',
      to: [{type: 'item'}]
    }
  ]
}
Thank you for your reply! Unfortunately, I couldn't use the new page (it requires another request to Sanity, and I couldn't do that). I added an object to
myType
, which stores a
boolean
and a
datetime
. Every time someone moves the
boolean
to
true
, the current
datetime
is overwritten. On BE we will simply do a filtering of
myType
by
boolean = true
and take the object with the latest
datetime
.
Yes, that's also a way to solve it.
As a side note, you could have combined the query:
{
"featuredItem": *[_type == "singleton"][0] { featuredItem }
"items": *[_type == "item"]
}

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?