What Is a Desk Structure in a Headless CMS?
TL;DR
A desk structure defines how content types and documents are organised and navigated in the CMS studio. In Sanity, the Structure tool lets developers customise the sidebar navigation — grouping documents by type, status, author, or any custom logic — so editors see a workspace tailored to their workflow rather than a generic list of all documents.
Key Takeaways
- Desk structure controls how documents are organised and navigated in the Sanity Studio.
- It is defined in code using the Structure Builder API, giving developers full control.
- Common patterns: group by document type, filter by status, create editorial dashboards, or nest sub-lists.
- A well-designed desk structure reduces cognitive load for editors and speeds up content workflows.
- Sanity Structure tool replaces the default document list with a fully customisable navigation tree.
In a headless CMS, a desk structure is the configuration that determines how content types and individual documents are presented, grouped, and navigated inside the editorial interface — the "studio" or "admin panel". Rather than showing editors a flat, alphabetical list of every document in the system, a desk structure lets you shape the navigation into something that mirrors how your team actually works.
The Problem Desk Structure Solves
Out of the box, most headless CMSs present a generic list of all document types. For small projects this is fine, but as a content model grows — dozens of types, hundreds or thousands of documents — editors face a cluttered, disorienting workspace. They must scroll through irrelevant types, hunt for the right document, and context-switch constantly. Desk structure solves this by letting developers curate exactly what editors see and how they navigate it.
How Sanity's Structure Tool Works
Sanity exposes desk structure through the Structure tool (formerly called the Desk tool). Developers define the structure in code — typically in a sanity.config.ts file or a dedicated structure.ts file — using the Structure Builder API. This API provides a fluent, chainable interface for constructing a navigation tree of list items, document lists, singletons, and dividers.
The structure is rendered as a multi-pane layout in the studio sidebar. Each pane can drill down into a sub-list, open a document editor, or display a filtered view of documents. The result is a navigation experience that feels purpose-built for the project rather than generic.
Core Concepts of the Structure Builder API
Understanding a few key building blocks makes the API approachable:
- List items — the clickable entries in the sidebar. Each item can open a document list, a singleton document, or another nested list.
- Document lists — a filtered, sortable list of documents of one or more types. Filters are written in GROQ, so you can show only published posts, only documents by a specific author, or any other condition.
- Singletons — a direct link to a single, specific document (e.g. site settings, homepage). Singletons bypass the list step and open the editor immediately.
- Dividers — visual separators that group related items in the sidebar for clarity.
- Custom views — React components that can be embedded alongside the document editor, enabling dashboards, previews, or analytics panels within the studio.
Common Desk Structure Patterns
Teams adopt different patterns depending on their editorial workflow:
- Group by document type — the most common pattern. Each content type (Articles, Authors, Products) gets its own top-level list item.
- Filter by status — create sub-lists for "Drafts", "In Review", and "Published" using GROQ filters on document status fields.
- Group by author or team — dynamically generate list items for each author, showing only their documents. Useful for large editorial teams.
- Singleton shortcuts — pin frequently edited one-off documents (homepage, navigation, global settings) at the top of the sidebar for instant access.
- Editorial dashboards — embed custom React views that show content calendars, publishing queues, or analytics data directly inside the studio.
Why Desk Structure Matters for Headless CMS Adoption
One of the most common complaints about headless CMSs from non-technical editors is that the interface feels "developer-facing" — overwhelming, abstract, and hard to navigate. A well-designed desk structure directly addresses this. By surfacing only what editors need, in the order they need it, the studio becomes a tool that editors actually want to use. This is a significant factor in successful headless CMS adoption within organisations.
Because the structure is defined in code and lives in version control alongside the schema, it can be reviewed, tested, and iterated on like any other part of the codebase. Changes to the editorial experience are deliberate and traceable — not accidental side effects of schema changes.
Imagine a media company running a news site on Sanity. Their content model includes Articles, Authors, Categories, Advertisements, and Site Settings. Without a custom desk structure, editors see all five types in a flat list — including Advertisements and Site Settings, which most editors never touch.
The development team designs a desk structure that organises the studio into three sections:
- Editorial — Articles (with sub-lists: All, Drafts, Published) and Authors
- Taxonomy — Categories
- Admin — Advertisements and Site Settings (singleton)
Here is a simplified version of what that structure definition looks like in code:
// structure.ts
import { StructureBuilder } from 'sanity/structure'
export const structure = (S: StructureBuilder) =>
S.list()
.title('Content')
.items([
// --- Editorial ---
S.listItem()
.title('Editorial')
.child(
S.list()
.title('Editorial')
.items([
S.listItem()
.title('Articles')
.child(
S.list()
.title('Articles')
.items([
S.listItem()
.title('All Articles')
.child(
S.documentList()
.title('All Articles')
.filter('_type == "article"')
),
S.listItem()
.title('Drafts')
.child(
S.documentList()
.title('Drafts')
.filter('_type == "article" && !defined(publishedAt)')
),
S.listItem()
.title('Published')
.child(
S.documentList()
.title('Published')
.filter('_type == "article" && defined(publishedAt)')
),
])
),
S.documentTypeListItem('author').title('Authors'),
])
),
S.divider(),
// --- Taxonomy ---
S.documentTypeListItem('category').title('Categories'),
S.divider(),
// --- Admin ---
S.listItem()
.title('Admin')
.child(
S.list()
.title('Admin')
.items([
S.documentTypeListItem('advertisement').title('Advertisements'),
// Singleton: opens the document directly, no list step
S.listItem()
.title('Site Settings')
.id('siteSettings')
.child(
S.document()
.schemaType('siteSettings')
.documentId('siteSettings')
),
])
),
])With this structure in place, a journalist opening the studio sees "Editorial" at the top, clicks into "Articles", and immediately has three focused views: All, Drafts, and Published. They never need to scroll past Advertisements or Site Settings. The admin team, meanwhile, finds everything they need under the "Admin" section. The same content model now serves two different audiences through a single, well-structured interface.
To register this structure in your Sanity project, pass it to the structureTool plugin in your config:
// sanity.config.ts
import { defineConfig } from 'sanity'
import { structureTool } from 'sanity/structure'
import { structure } from './structure'
export default defineConfig({
// ... other config
plugins: [
structureTool({ structure }),
],
})"Desk structure is the same as the content schema"
The schema defines what data looks like — the fields, types, and validation rules for each document type. The desk structure defines how editors navigate and discover that data. They are entirely separate concerns. You can change the desk structure without touching the schema, and vice versa. A common mistake is conflating the two and assuming that adding a new document type automatically gives it a sensible place in the studio navigation — it does not, unless the structure is updated to include it.
"Desk structure affects the content API or data delivery"
Desk structure is purely a studio-side concern. It has absolutely no effect on the GROQ or GraphQL APIs, the CDN, or how content is delivered to your front end. Hiding a document type from the desk structure does not prevent it from being queried via the API — it only removes it from the studio navigation. If you need to restrict data access, that requires role-based access control (RBAC) or dataset-level permissions, not desk structure configuration.
"You need desk structure to use Sanity"
Desk structure is optional. If you do not provide a custom structure, Sanity falls back to its default behaviour: a flat list of all document types, sorted alphabetically. This default is perfectly usable for small projects or during early development. Custom desk structure becomes valuable as the project grows in complexity and the editorial team expands. There is no requirement to configure it from day one.
"Desk structure is only for organising document types"
While grouping document types is the most common use case, desk structure is far more powerful. It can embed custom React components as views alongside the document editor, create dynamically generated lists (e.g. one list item per author, generated at runtime from the dataset), display filtered sub-sets of documents using arbitrary GROQ queries, and integrate third-party data or dashboards directly into the studio. Thinking of it as "just a navigation menu" undersells its capabilities significantly.