Type something to search...
How to Organize Fields With Groups and Fieldsets in Sanity

How to Organize Fields With Groups and Fieldsets in Sanity

To organise fields in Sanity, use field groups and fieldsets. Field groups add tabs to the top of a document or object form: you declare them in a groups array on the type and assign each field to one or more groups with its group property. Fieldsets visually cluster related fields inside the form: you declare them in a fieldsets array and assign fields with the fieldset property, and you can make them collapsible or lay them out in columns. Neither changes how data is stored, so you can reorganise the form at any time without migrating content.

Long documents are hard to edit. A landing page with SEO fields, social images, a hero section, page sections and publishing settings can easily reach 30 fields, and editors end up scrolling past things they don't need. Groups and fieldsets fix that without changing your data model. This article explains the difference between the two, how to define each, the options available, how to combine them and some sensible conventions.

Groups vs Fieldsets

The two features solve related but different problems.

Field groupsFieldsets
What editors seeTabs at the top of the formA titled box around fields
Defined ingroups on the typefieldsets on the type
Assigned withgroup: 'name' or group: ['a', 'b']fieldset: 'name'
A field can belong toSeveral groupsOne fieldset
Fields outside themAppear under the All fields tabAppear normally in the form
Optionsdefault, icon, hiddencollapsible, collapsed, columns, hidden, readOnly
Affects stored dataNoNo

A good mental model: groups split a form into views, and fieldsets arrange fields within a view. The key point in the last row is worth repeating. A field in a group or fieldset is stored at the same path as before. If you need fields nested under a key like seo.title, that's an object field, not a fieldset; see building reusable object types in Sanity for that.

Defining Field Groups

Declare groups on the type, then reference them from fields:

// schemaTypes/page.ts
import { defineField, defineType } from 'sanity'
import { CogIcon, DocumentTextIcon, SearchIcon } from '@sanity/icons'

export const page = defineType({
  name: 'page',
  title: 'Page',
  type: 'document',
  groups: [
    { name: 'content', title: 'Content', icon: DocumentTextIcon, default: true },
    { name: 'seo', title: 'SEO', icon: SearchIcon },
    { name: 'settings', title: 'Settings', icon: CogIcon },
  ],
  fields: [
    defineField({ name: 'title', type: 'string', group: ['content', 'seo'] }),
    defineField({ name: 'slug', type: 'slug', options: { source: 'title' }, group: 'settings' }),
    defineField({ name: 'body', type: 'array', of: [{ type: 'block' }], group: 'content' }),
    defineField({ name: 'seoTitle', title: 'SEO title', type: 'string', group: 'seo' }),
    defineField({ name: 'seoDescription', title: 'SEO description', type: 'text', rows: 3, group: 'seo' }),
    defineField({ name: 'noIndex', title: 'Hide from search engines', type: 'boolean', group: 'seo' }),
    defineField({ name: 'publishedAt', type: 'datetime', group: 'settings' }),
  ],
})

Install the icons package if you haven't already:

npm install @sanity/icons

The form now shows four tabs: All fields, Content, SEO and Settings. A few details:

  • default: true opens that tab first. Without it, the form opens on All fields.
  • Multiple groups: title appears under both Content and SEO, because editors often want to see it while writing a meta description.
  • Ungrouped fields only appear under All fields, which is easy to miss. Assign every field to at least one group once you start using groups.
  • Icons appear next to tab titles. They're optional but help editors recognise tabs quickly.

Groups on Object Types

Groups work on object types too, not just documents. This is handy for big reusable objects, like a hero section with content and design options:

// schemaTypes/objects/hero.ts
import { defineField, defineType } from 'sanity'

export const hero = defineType({
  name: 'hero',
  type: 'object',
  groups: [
    { name: 'content', title: 'Content', default: true },
    { name: 'design', title: 'Design' },
  ],
  fields: [
    defineField({ name: 'heading', type: 'string', group: 'content' }),
    defineField({ name: 'subheading', type: 'text', rows: 2, group: 'content' }),
    defineField({ name: 'image', type: 'image', options: { hotspot: true }, group: 'content' }),
    defineField({
      name: 'theme',
      type: 'string',
      options: { list: ['light', 'dark'], layout: 'radio' },
      initialValue: 'light',
      group: 'design',
    }),
  ],
})

Hiding a Group

Groups accept hidden, either as a boolean or a callback. You might hide an advanced tab from non-administrators:

groups: [
  { name: 'content', title: 'Content', default: true },
  {
    name: 'advanced',
    title: 'Advanced',
    hidden: ({ currentUser }) =>
      !currentUser?.roles.some((role) => role.name === 'administrator'),
  },
],

Hiding the tab only hides the tab. Fields in that group still appear under All fields, so hide the fields themselves too if they shouldn't be editable. Conditional fields in Sanity covers the callback options in detail.

Defining Fieldsets

Fieldsets are declared the same way, with a fieldsets array:

// schemaTypes/event.ts
import { defineField, defineType } from 'sanity'

export const event = defineType({
  name: 'event',
  title: 'Event',
  type: 'document',
  fieldsets: [
    {
      name: 'schedule',
      title: 'Schedule',
      description: 'When the event starts and ends',
      options: { columns: 2 },
    },
    {
      name: 'venue',
      title: 'Venue',
      options: { collapsible: true, collapsed: false },
    },
    {
      name: 'tickets',
      title: 'Tickets',
      options: { collapsible: true, collapsed: true },
    },
  ],
  fields: [
    defineField({ name: 'name', type: 'string' }),
    defineField({ name: 'startsAt', type: 'datetime', fieldset: 'schedule' }),
    defineField({ name: 'endsAt', type: 'datetime', fieldset: 'schedule' }),
    defineField({ name: 'venueName', type: 'string', fieldset: 'venue' }),
    defineField({ name: 'address', type: 'text', rows: 2, fieldset: 'venue' }),
    defineField({ name: 'ticketUrl', type: 'url', fieldset: 'tickets' }),
    defineField({ name: 'price', type: 'number', fieldset: 'tickets' }),
  ],
})

Fieldset Options

  • columns: lays fields out side by side. Two columns work well for short fields such as start and end dates or first and last names.
  • collapsible: adds a toggle so editors can fold the fieldset away.
  • collapsed: sets the initial state. collapsed: true keeps rarely used fields out of the way until needed.
  • hidden and readOnly: accept a boolean or a callback, and apply to every field in the fieldset.

In the stored document, the fields are flat:

{
  "_type": "event",
  "name": "Sanity Meetup",
  "startsAt": "2026-12-01T18:00:00Z",
  "endsAt": "2026-12-01T21:00:00Z",
  "venueName": "Town Hall",
  "ticketUrl": "https://example.com/tickets",
  "price": 0
}

So your GROQ queries don't mention fieldsets at all:

*[_type == "event" && startsAt > now()] | order(startsAt asc){
  name,
  startsAt,
  venueName,
  price
}

Order Matters

A fieldset appears in the form where its first field appears in fields. Keep a fieldset's fields next to each other in the array, otherwise you get confusing layouts where the fieldset box sits in one place but logically belongs elsewhere.

Collapsing Object Fields

Object fields behave much like fieldsets visually, and they accept the same kind of options. A nested seo object can be collapsible:

defineField({
  name: 'seo',
  type: 'object',
  options: { collapsible: true, collapsed: true },
  fields: [
    defineField({ name: 'title', type: 'string' }),
    defineField({ name: 'description', type: 'text', rows: 3 }),
  ],
})

The difference is in the data: this stores seo.title and seo.description. Choose an object when the fields belong together as data, and a fieldset when they only belong together in the editor.

Combining Groups and Fieldsets

You can use both on the same type. Assign the fieldset to a group with its own group property, and every field inside follows:

export const product = defineType({
  name: 'product',
  type: 'document',
  groups: [
    { name: 'details', title: 'Details', default: true },
    { name: 'pricing', title: 'Pricing' },
  ],
  fieldsets: [
    {
      name: 'price',
      title: 'Price',
      group: 'pricing',
      options: { columns: 2 },
    },
  ],
  fields: [
    defineField({ name: 'name', type: 'string', group: 'details' }),
    defineField({ name: 'description', type: 'text', group: 'details' }),
    defineField({ name: 'amount', type: 'number', fieldset: 'price' }),
    defineField({
      name: 'currency',
      type: 'string',
      options: { list: ['AUD', 'GBP', 'USD', 'EUR'] },
      fieldset: 'price',
    }),
  ],
})

The Pricing tab shows a two-column Price fieldset containing amount and currency.

Validation and Hidden Tabs

Validation runs on every field regardless of which tab is open, which can confuse editors when the Publish button is disabled by an error on a tab they can't see. The Studio helps by showing a validation indicator on tabs that contain errors, and by listing all problems in the validation panel. Even so, it's worth putting required fields on the default tab where possible.

For the rules themselves, see how to add validation rules to Sanity fields.

Conventions That Work

After using groups and fieldsets across a few projects, these conventions hold up well:

  1. Start without them. Small documents with fewer than about eight fields don't need tabs. Add groups when scrolling becomes a problem.
  2. Use a consistent set of group names across document types, such as content, media, seo and settings. Editors learn where things live once.
  3. Always set a default group so the form opens on the most-used tab instead of All fields.
  4. Put every field in a group once you introduce groups, so nothing is only reachable through All fields.
  5. Collapse rarely used fieldsets with collapsed: true, but keep anything required visible.
  6. Use columns for short paired fields only. Long text inputs in columns become cramped.
  7. Prefer objects for data structure and fieldsets for layout. Don't use fieldsets to fake nesting.

Shared group definitions keep naming consistent. Export them once and reuse them:

// schemaTypes/shared/groups.ts
import { CogIcon, DocumentTextIcon, ImageIcon, SearchIcon } from '@sanity/icons'

export const CONTENT_GROUP = { name: 'content', title: 'Content', icon: DocumentTextIcon, default: true }
export const MEDIA_GROUP = { name: 'media', title: 'Media', icon: ImageIcon }
export const SEO_GROUP = { name: 'seo', title: 'SEO', icon: SearchIcon }
export const SETTINGS_GROUP = { name: 'settings', title: 'Settings', icon: CogIcon }

These patterns are part of a broader approach to schema design, covered in content modeling best practices in Sanity.


FAQ: Groups and Fieldsets in Sanity

No. Both only affect how the form looks in Sanity Studio. Fields are stored at the same path whether or not they're in a group or fieldset, so you can reorganise them without migrating content.

Yes. Set group to an array of group names, such as group: ['content', 'seo'], and the field appears in each of those tabs. A field can only belong to one fieldset, though.

Fields without a group property don't belong to any tab, so they only appear in the All fields view. Assign every field to at least one group once you start using groups.

In the fieldset definition, set both collapsible and collapsed to true inside its options. Editors can still expand it with a click.

Yes. Object types accept groups and fieldsets just like documents, which helps when a reusable object, such as a page section, has many fields.

Use a fieldset when fields only belong together visually and should stay at the top level of the document. Use an object when they belong together as data and should be stored and queried under a shared key.


Conclusion

Field groups and fieldsets are the simplest way to make a large Sanity document comfortable to edit. Groups split the form into tabs so editors only see what's relevant to the task in hand, and fieldsets cluster related fields with options for columns and collapsing. Because neither affects stored data, you can introduce or rearrange them at any time.

Start with plain fields, add groups when documents get long, set a default tab, and keep your group names consistent across types. Reserve fieldsets for layout and objects for structure, and your Studio will stay tidy as the schema grows.

Tags :
Share :

Related Posts

Building a Static Site With Astro and Sanity

Building a Static Site With Astro and Sanity

To build a static site with Astro and Sanity, keep Astro's default output: 'static', configure @sanity/astro with useCdn: false, and fetch

Dive Deeper
Common Sanity Mistakes and How to Fix Them

Common Sanity Mistakes and How to Fix Them

The most common Sanity mistakes are using the CDN where you need fresh data, fetching whole documents with ..., leaving slugs unvalidated, ship

Dive Deeper
How to Create a Custom Sanity Plugin

How to Create a Custom Sanity Plugin

To create a custom Sanity plugin, wrap a piece of Studio configuration in definePlugin from the sanity package and add it to the plugins ar

Dive Deeper