DEV Community

Cover image for A Guide to Field Groups and Dynamic Customization in Sanity.io
William Iommi
William Iommi

Posted on • Updated on

A Guide to Field Groups and Dynamic Customization in Sanity.io

Hello everyone! In this brief article, we will explore how to customize a feature offered by Sanity.io.

The feature in question is called Field Groups. This functionality allows you to define groups within your document/object and associate them with individual fields. As a result, within the Studio, the fields are visually separated into tabs.

This option is incredibly useful when you want to group fields that are related to each other (a classic example being an SEO group where there are only fields related to the specific theme).

In addition to the groups you define, the platform inserts an uncustomizable 'All Fields' group in the first position, which contains all fields regardless of their group membership.

However, there's currently one thing that isn't possible (unless done manually for each individual document) and which I believe could be quite useful — having a dynamic group dedicated to fields that aren't associated with any of the custom groups, for any reason.

Here's what we'll cover today:

  • Creation of a dynamic group called 'Unmapped fields'
  • Bonus: Through a small hack, we will remove the 'All fields' group.

🧑‍💻 Create a dynamic group

One of the things I appreciate about Sanity is its high level of customization flexibility. As mentioned earlier, we don't want to create this group for every document we have, but since the Sanity Studio is 'just' Javascript, we can find a way to achieve what we want.

Let's start by defining what our function should do:

  • Receive our documents/objects as input
  • Determine whether each document has fields without an associated group
  • Create the dynamic group and place it at the end of the available custom groups
  • Associate this group with the necessary fields.
// some-utility-method-file-blah-blah.ts
import {DocumentDefinition, FieldGroupDefinition, ObjectDefinition} from 'sanity'
import {LinkRemovedIcon} from '@sanity/icons'

// this is the definition of our custom group for unmapped fields
const unmappedFieldsGroup: FieldGroupDefinition = {
  name: 'unmapped-fields',
  icon: LinkRemovedIcon,
  title: "Unmapped Fields"
}


type DocumentObjectDefinitionType = DocumentDefinition | ObjectDefinition

export const enhancedGroupsDefinition = (definitions: DocumentObjectDefinitionType[]): DocumentObjectDefinitionType[]  => {

  const doMagic = (def: DocumentObjectDefinitionType) => {
    // Check if your document/object has any groups defined.
    if (def.groups && Array.isArray(def.groups) && def.groups.length > 0) {

      // Check if every field has a group defined.
      const everyFieldsHaveGroup = !!def.fields.every(
        (field) =>
          typeof field.group === 'string' || (Array.isArray(field.group) && field.group.length > 0)
      )

      // If some fields don't have a group, we can add our custom group.
      if (!everyFieldsHaveGroup) {
        // Add our custom group at the end of the list.
        def.groups.push(unmappedFieldsGroup)
        // Loop through all fields and add the unmapped group if a group is not defined.
        def.fields = def.fields.map((field) => {
          if (!field.group || (Array.isArray(field.group) && field.group.length === 0))
            field.group = unmappedFieldsGroup.name
          return field
        })
      }
    }
    return def
  }

    // map every definitions with our doMagic function
  return definitions.map(doMagic)
}
Enter fullscreen mode Exit fullscreen mode

Now we can go to the 'sanity.config.ts' file (or wherever we have defined our schemas) and enhance our definitions.

// sanity.config.js
import {enhancedGroupsDefinition} from '../../some-utility-file-blah-blah'

defineConfig({
  // your configuration
 schema: {
    types: [
      ...enhancedGroupsDefinition([Document1, Document2]),
    ],
  },
})
Enter fullscreen mode Exit fullscreen mode

What we achieve is a dynamic group that contains only the unmapped fields and disappears as soon as all fields are associated with a group. Naturally, being a custom group, you can change its name or icon as you prefer.


⭐️ Bonus: Remove the 'All fields' group

 

🚨 I consider this to be a minor 'hack', so please take this into consideration. Currently, I have only noticed a minor side effect that you should be aware of, and I will explain it later.

To remove the group, we need to make changes within the 'sanity.config.ts' file and modify the following object form->components->input like this:

// sanity.config.js
import {isObjectInputProps} from 'sanity'
import {enhancedGroupsDefinition} from '../../some-utility-method-file-blah-blah'

defineConfig({
  // your configuration
  form: {
    components: {
      input: (props) => {
        if (isObjectInputProps(props) && Array.isArray(props.groups) && props.groups.length > 0) {
          if (props.groups[0].name === 'all-fields') {
            props.groups.shift()
          }
        }
        return props.renderDefault(props)
      },
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

This is the final result:

Unmapped fields

 

🚨 Known issue:

As mentioned earlier, with the removal of the 'All fields' group, I have currently noticed only one side effect that occurs when the 'Review changes' sidebar is opened. Here are the differences:

Before

Before

After

After

👋 Conclusion

Today we saw how even with a simple function, it's possible to enhance your experience within the Sanity Studio by introducing a customization that can be useful for your clients. This is one of the things I like the most about Sanity.

Thanks for reading!

See ya 🤙

Top comments (0)