sanity-singletons
Configure singleton documents in a Sanity Studio using the document.singletons registry.
Skill by sanity-ioOfficial
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/sanity-io-sanity-singletons
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: documents, backend, writing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/sanity-singletons
What's inside
- Skill name
sanity-singletons- Description
- Configure singleton documents in a Sanity Studio using the document.singletons registry. Use when a developer wants a fixed-id document (settings, navigation, footer) edited in place, hidden from the default content list, with no "create new" or "duplicate" affordances — or when migrating a userland singleton implementation or a third-party singleton plugin (e.g. sanity-plugin-singleton-management) to the first-class API.
- License
- MIT
- Instructions
- 196 lines in SKILL.md · view on GitHub
Files (1)
- SKILL.md
SKILL.md (first lines)
# Sanity Singletons
A "singleton" is a document with a fixed id that can be created exactly once. Common examples: site settings, navigation, footer content. Studio supports singletons as a first-class primitive via the `document.singletons` configuration registry plus a small set of Structure Tool helpers.
**Requires `sanity` 6.17.0 or later.** Check the installed version in the lockfile before using this API. On older Studios, either upgrade first or use the userland pattern (fixed `documentId` in a custom structure, plus manual `newDocumentOptions` and `duplicate` filtering) described in `sanity-best-practices` → `studio-structure`.
## When to use this skill
- A developer asks how to create a "settings document" or "singleton".
- A studio configuration needs a `document.singletons` registry entry.
- Structure Tool needs to render a singleton as a list item or document pane.
- You're migrating an existing userland singleton implementation to the first-class API.
- You're migrating from a third-party singleton plugin such as `sanity-plugin-singleton-management` (or its predecessor `sanity-plugin-singleton-tools`).
## Registering a singleton
Singletons are registered in configuration, **not** on the schema type. A singleton definition binds a _definition id_ to a _document id_ and a _schema type_:
```ts
import {defineConfig, defineSingleton} from 'sanity'
export default defineConfig({
schema: {
types: [/* siteSettings and campaign are ordinary document types */],
},
document: {
singletons: [
// String shorthand — expands to
// {id: 'siteSettings', documentId: 'siteSettings', schemaType: 'siteSettings'}
'siteSettings',
// Definition object. `defineSingleton` is an optional typed identity
// helper; a plain object literal works too. `id` is omitted here, so it
// inherits each `documentId`.
defineSingleton({
documentId: 'springCampaign',
schemaType: 'campaign',
title: 'Spring campaign', // optional display metadata
initialValue: {season: 'spring'}, // optional per-singleton initial value
}),
defineSingleton({
documentId: 'summerCampaign',
schemaType: 'campaign',
title: 'Summer campaign',
initialValue: {season: 'summer'},
}),
],
},
})
```
Key facts:
- **`defineSingleton` produces a singleton _definition_, not a document schema.** The schema type it references is defined separately with `defineType` and needs no singleton-specific configuration.
- **Multiple singletons may share a schema type** (like the two campaigns above), each with its own `title`, `icon`, and `initialValue` (falling back to the schema type's), and a shared schema type may also back ordinary documents.
- **The definition id is the singleton's stable identity**, and it is **optional** — it inherits `documentId` when omitted. Structure code references it (`S.listItem().singleton('springCampaign')`). Set it explicitly only to be verbose, to guard against future collisions, or to address a singleton universally when different workspaces or environments map it to different document ids.
- A resolver function is also accepted: `singletons: (prev, context) => [...prev, ...]`. Resolvers only ever receive fully resolved definitions (string shorthands are expanded first).
Validation at config-resolution time (all failures aggregate into one `ConfigResolutionError`):
- `id`: non-empty, unique across definitions (inherited ids count — an explicit id may not collide with another definition's inherited one). Used verbatim as a Structure Tool node id, so it is limited to `[a-zA-Z0-9._-]` and may not start with `__edit__`. It also may not match the name of a document type that no singleton claims, since the default content list keys both by id (the string shorthand never trips this — the type it names is claimed by the definition itself).
- `documentId`: non-empty, unique, published-id shaped (no `drafts.` / `versions.` prefix, only `[a-zA-Z0-9._-]`).
- `schemaType`: must name an existing document type. Need not be unique.
- Templates: an untagged template must not reuse a definition id (the generated singleton template claims it); at most one template may carry a given `singleton` tag; tags must reference registered definitions.
## What Studio does automatically
1. **Generates a dedicated initial value template per singleton** — tagged with the definition id via `ResolvedTemplate.singleton`, value from `definition.initialValue ?? schemaType.initialValue` — instead of the plain per-type template, and **removes tagged templates from every "create new" surface** (global create menu, structure panes, reference inputs). The tagged template stays in `source.templates`, so opening the singleton before it exists still applies its initial value.
2. **Injects `context.singleton`** (the definition id) into document-scoped configuration contexts — `document.actions`, `badges`, `inspectors`, field actions, `comments.enabled`, `askToEdit.enabled` — looked up by _published_ document id, so drafts and release versions of the singleton match, however the document was opened (structure, intent, deep link, search).
3. **Filters the `duplicate` document action**, after all user resolvers run, so it cannot be reintroduced via `document.actions`.
4. **Hides the schema type from the implicit default content list** in Structure Tool, and **appends the singleton itself to that list** — one item per definition, after the document type lists, in registration order, each equivalent to `S.listItem().singleton(id)`. A studio that has not configured Structure Tool needs no structure code at all. This applies only to the default structure; a `structure` resolver replaces it wholesale.
What Studio deliberately does **not** do:
- **Delete, unpublish, and discard stay available.** Deleting a singleton is a legitimate "reset": structure still points at the fixed id, and editing recreates the document. To hide delete as well: `document: {actions: (prev, context) => (context.singleton ? prev.filter((action) => action.action !== 'delete') : prev)}`.
- **Explicit structure is never filtered.** `S.documentTypeList(typeName)` works for singleton schema types and intentionally lists the singleton documents alongside ordinary ones.
- **No warning for "dangling" singletons.** Structure resolves lazily, so Studio cannot detect a registered singleton that a _custom_ structure never adds. (The default structure adds them all, so this only affects studios with a `structure` resolver.) Any heuristic short of eager resolution would misfire, because manual `S.document().documentId(...).schemaType(...)` wiring is supported. In a custom structure, always pair a registry entry with a structure entry.
## Surfacing singletons in Structure Tool
**With the default structure there is nothing to do** — registered singletons are appended to the top-level content list automatically.
In a **custom structure** (any studio passing a `structure` resolver to `structureTool()`), add them explicitly. All helpers are keyed by **definition id** and throw a `SerializeError` for unknown ids.
Watch for this trap: `S.documentTypeListItems()`, the usual way to reproduce the default lists inside a custom structure, excludes singleton schema types and does **not** add singleton items. `S.list().items([...S.documentTypeListItems()])` therefore drops every singleton. Use `.singletons()` alongside it:
```ts
S.list().id('__root__').title('Content').items(S.documentTypeListItems()).singletons()
```
```ts
// 1. Highest level: a list of singletons — a given set, or all of them.
S.list().id('singletons').title('Singletons').singletons(['siteSettings', 'springCampaign'])
S.list().id('singletons').title('Singletons').singletons()
// 2. A single list item (title/icon default from the definition, then the schema type).
S.listItem().singleton('siteSettings')
// 3. Low level: just the document pane.
S.listItem().title('Settings').id('settings').child(S.document().singleton('siteSettings'))
```
Composition rules:
- Every default is overridable by normal chaining: `S.listItem().singleton('siteSettings').title('Global settings')`, `S.document().singleton('siteSettings').documentId('override')`.
- **Titles are not disambiguated for you.** The list item id defaults to the definition id (unique by construction), but the title falls back to the schema type's, so singletons sharing a schema type share a label. Give each definition a `title` — it applies everywhere the singleton is rendered, including `S.list().singletons()`, which has no per-item overrides.
- `S.document().singleton()` pins the singleton's tagged initial value template so its initial value applies even when the type has several templates; override with `.initialValueTemplate(...)`.
- **`S.list().singletons()` returns a builder without `.items()`.** Calling `.items()` afterwards would replace the whole array and silently drop the singleton items, so the type forbids it. Call `.items([...])` _before_ `.singletons([...])`, or put `S.listItem().singleton()` entries inside a regular `.items()` array. Repeated `.singletons()` calls append.
- **`singletons()` with no argument means every registered definition** (registration order, including plugin-registered ones); `singletons([])` means none. Combining it with a hand-written `S.listItem().singleton(id)` in the same list adds that singleton twice and fails with a duplicate-id `SerializeError` — pass an explicit array when mixing.
- **`S.documentListItem().singleton(id)`** renders a live document preview instead of a static title, and defaults its id to the definition's `documentId` (not the definition id), because a document list item's id _is_ the document it previews. Prefer plain `S.listItem()` when the singleton document may not exist yet.
## Shared schema types and the "create new" escape hatch
Declaring a singleton over schema type `T` replaces `T`'s plain per-type template with the singleton's tagged template, so `T` disappears from create menus. If ordinary `T` documents should stay creatable, define an explicit untagged template — any id works, including `T` itself, since the plain template no longer exists:
```ts
schema: {
templates: (prev) => [
...prev,From sanity-io/agent-toolkit, MIT.
More from sanity-io/agent-toolkit
- 0.794score
sanity-best-practices
sanity-ioOfficial
Sanity development best practices for schema design, GROQ queries, TypeGen, Visual Editing, images, Portable Text, Studio structure, localization, migrations…
SkillAFreshness: A★ 194Claude Code +9 - 0.794score
Sanity
www.sanity.ioOfficial
Direct access to your Sanity projects (content, datasets, releases, schemas) and agent rules
ConnectorAFreshness: ARemote★ 194Claude Code +3 - 0.789score
sanity-form-component-suspense
sanity-ioOfficial
Fix a Sanity Studio custom form component (components.input, components.field, components.item, or a plugin's form.components) that suspends without its own…
SkillAFreshness: A★ 194Claude Code +9 - 0.749score
sanity-studio-upgrade
sanity-ioOfficial
Produces a tailored Sanity Studio upgrade plan by inspecting the repository's installed versions, config, and source, then reporting only the breaking changes…
SkillBFreshness: B★ 194Claude Code +9 - 0.749score
content-experimentation-best-practices
sanity-ioOfficial
Content experimentation and A/B testing guidance covering experiment design, hypotheses, metrics, sample size, statistical foundations, CMS-managed variants…
SkillBFreshness: B★ 194Claude Code +9 - 0.749score
sanity-migration
sanity-ioOfficial
Plans, implements, and reviews migrations from other CMSes and content systems into Sanity.
SkillBFreshness: B★ 194Claude Code +9 - 0.743score
portable-text-conversion
sanityOfficial
Convert HTML and Markdown content into Portable Text blocks for Sanity.
SkillBFreshness: B★ 194Claude Code +9 - 0.616score
seo-aeo-best-practices
sanity-ioOfficial
SEO and AEO best practices for metadata, Open Graph, sitemaps, robots.txt, hreflang, JSON-LD structured data, EEAT, and content optimized for search engines…
SkillDFreshness: D★ 194Claude Code +9 - 0.616score
content-modeling-best-practices
sanity-ioOfficial
Structured content modeling guidance for schema design, content architecture, content reuse, references versus embedded objects, separation of concerns, and…
SkillDFreshness: D★ 194Claude Code +9 - 0.611score
portable-text-serialization
sanityOfficial
Render and serialize Portable Text to React, Svelte, Vue, Astro, HTML, Markdown, and plain text.
SkillDFreshness: D★ 194Claude Code +9
Signals
- Stars
- 194
- Forks
- 30
- Open issues
- 6
- Contributors
- 21
- Last commit (this folder)
- Oct 12, 2026 (16 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/sanity-io/agent-toolkit
- Website
- www.sanity.io
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from sanity-io
- 0.794score
sanity-best-practices
sanity-ioOfficial
Sanity development best practices for schema design, GROQ queries, TypeGen, Visual Editing, images, Portable Text, Studio structure, localization, migrations…
SkillAFreshness: A★ 194Claude Code +9 - 0.789score
sanity-form-component-suspense
sanity-ioOfficial
Fix a Sanity Studio custom form component (components.input, components.field, components.item, or a plugin's form.components) that suspends without its own…
SkillAFreshness: A★ 194Claude Code +9 - 0.749score
sanity-migration
sanity-ioOfficial
Plans, implements, and reviews migrations from other CMSes and content systems into Sanity.
SkillBFreshness: B★ 194Claude Code +9 - 0.749score
content-experimentation-best-practices
sanity-ioOfficial
Content experimentation and A/B testing guidance covering experiment design, hypotheses, metrics, sample size, statistical foundations, CMS-managed variants…
SkillBFreshness: B★ 194Claude Code +9 - 0.749score
sanity-studio-upgrade
sanity-ioOfficial
Produces a tailored Sanity Studio upgrade plan by inspecting the repository's installed versions, config, and source, then reporting only the breaking changes…
SkillBFreshness: B★ 194Claude Code +9 - 0.616score
content-modeling-best-practices
sanity-ioOfficial
Structured content modeling guidance for schema design, content architecture, content reuse, references versus embedded objects, separation of concerns, and…
SkillDFreshness: D★ 194Claude Code +9
Cite this page
sanity-singletons skill. Agent Mart. https://agentmart.dev/skills/sanity-io-sanity-singletons (last updated 2026-10-24)