sanity-singletons

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Sanity Singletons

Sanity Singleton 文档

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
.
“singleton” 是具有固定 id 且只能创建一次的文档。常见示例包括站点设置、导航和页脚内容。Studio 通过
document.singletons
配置注册表以及一组 Structure Tool helper,将 singleton 作为 first-class primitive 提供支持。
需要
sanity
6.17.0 或更高版本。
使用此 API 前,请检查 lockfile 中安装的版本。在较旧的 Studio 上,应先升级,或使用
sanity-best-practices
→
studio-structure
中所述的 userland 模式(在自定义 structure 中使用固定
documentId
,并手动过滤
newDocumentOptions
和
duplicate
)。

When to use this skill

何时使用此 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
    ).
  • 开发者询问如何创建“settings document”或“singleton”。
  • Studio 配置需要添加一个
    document.singletons
    registry entry。
  • Structure Tool 需要将 singleton 呈现为列表项或文档窗格。
  • 正在将现有的 userland singleton 实现迁移到 first-class API。
  • 正在从第三方 singleton 插件(如
    sanity-plugin-singleton-management
    或其前身
    sanity-plugin-singleton-tools
    )迁移。

Registering a singleton

注册 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.
Singleton 在 configuration 中注册,而不是在 schema type 上注册。singleton definition 将 definition id 绑定到 document id 和 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'},
      }),
    ],
  },
})
关键事实:
  • defineSingleton
    生成的是 singleton definition,而不是 document schema。
    它引用的 schema type 需要通过
    defineType
    单独定义,并且不需要任何 singleton 专用配置。
  • 多个 singleton 可以共享同一个 schema type(如上述两个 campaign),每个 singleton 都有自己的
    title
    、
    icon
    和
    initialValue
    (未设置时回退到 schema type 的值),同一个 schema type 也可以同时支持普通文档。
  • definition id 是 singleton 的稳定标识,且为可选——省略时会继承
    documentId
    。Structure 代码通过它进行引用(
    S.listItem().singleton('springCampaign')
    )。只有在需要显式定义、防止未来发生冲突,或需要在不同 workspace 或 environment 中将 singleton 映射到不同 document id 时,才应显式设置。
  • 也可以传入 resolver function:
    singletons: (prev, context) => [...prev, ...]
    。Resolver 只会收到完全解析后的 definitions(字符串 shorthand 会先被展开)。
在解析 configuration 时会执行以下验证(所有失败都会聚合到一个
ConfigResolutionError
中):
  • id
    :不能为空,且必须在所有 definitions 中保持唯一(包括继承的 id——显式 id 不能与另一个 definition 继承的 id 冲突)。它会原样用作 Structure Tool 节点 id,因此仅允许使用
    [a-zA-Z0-9._-]
    ,且不能以
    __edit__
    开头。它也不能与未被任何 singleton 声明的 document type 名称相同,因为默认内容列表同时使用 id 作为键(字符串 shorthand 不会触发此问题,因为它所命名的 type 已被该 definition 声明)。
  • documentId
    :不能为空,必须唯一,并且必须符合 published-id 格式(不能带有
    drafts.
    或
    versions.
    前缀,只能使用
    [a-zA-Z0-9._-]
    )。
  • schemaType
    :必须引用现有的 document type,但无需唯一。
  • Templates:未带 tag 的 template 不能复用 definition id;每个
    singleton
    tag 最多只能由一个 template 携带;tag 必须引用已注册的 definition。

What Studio does automatically

Studio 会自动完成的工作

  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.
  1. 为每个 singleton 生成专用的初始值 template——通过
    ResolvedTemplate.singleton
    使用 definition id 作为 tag,值取自
    definition.initialValue ?? schemaType.initialValue
    ,而不是普通的 per-type template;同时会从所有“create new”入口中移除带 tag 的 templates(全局创建菜单、structure pane、reference input)。带 tag 的 template 仍保留在
    source.templates
    中,因此在 singleton 尚不存在时打开它,仍会应用其初始值。
  2. 将
    context.singleton
    (definition id)注入 document-scoped configuration contexts——
    document.actions
    、
    badges
    、
    inspectors
    、field actions、
    comments.enabled
    、
    askToEdit.enabled
    。查找依据是已发布文档的 id,因此无论通过何种方式打开文档(structure、intent、deep link、search),singleton 的 draft 和 release version 都能正确匹配。
  3. 在所有 user resolver 运行完毕后过滤
    duplicate
    document action
    ,因此无法通过
    document.actions
    重新引入该 action。
  4. 在 Structure Tool 中将 schema type 从隐式默认内容列表中隐藏,并将 singleton 自身追加到该列表——每个 definition 对应一个条目,位于 document type 列表之后并按注册顺序排列,每个条目都等同于
    S.listItem().singleton(id)
    。未配置 Structure Tool 的 Studio 完全不需要编写 structure 代码。此行为仅适用于默认 structure;
    structure
    resolver 会完全替换默认 structure。
Studio 刻意不会执行以下操作:
  • Delete、unpublish 和 discard 操作仍然可用。 删除 singleton 是一种合法的“重置”:structure 仍指向固定 id,再次编辑时会重新创建文档。如需同时隐藏 delete:
    document: {actions: (prev, context) => (context.singleton ? prev.filter((action) => action.action !== 'delete') : prev)}
    。
  • 显式 structure 永远不会被过滤。
    S.documentTypeList(typeName)
    可用于 singleton schema type,并会有意将 singleton 文档与普通文档一起列出。
  • 不会对“悬空”的 singleton 发出警告。 Structure 是惰性解析的,因此 Studio 无法检测到已注册但自定义 structure 从未添加的 singleton。(默认 structure 会添加全部 singleton,因此这只影响使用
    structure
    resolver 的 Studio。)任何不够 eager resolution 的启发式方案都会产生误判,因为手动
    S.document().documentId(...).schemaType(...)
    接线受到支持。在自定义 structure 中,始终应将 registry entry 与 structure entry 配对。

Surfacing singletons in Structure Tool

在 Structure Tool 中呈现 singleton

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.
使用默认 structure 时无需任何操作——注册的 singleton 会自动追加到顶层内容列表。
在自定义 structure(任何向
structureTool()
传入
structure
resolver 的 Studio)中,需要显式添加它们。所有 helper 均以 definition id 为键,遇到未知 id 时会抛出
SerializeError
。
请注意以下陷阱:
S.documentTypeListItems()
是在自定义 structure 中复现默认列表的常用方式,但它会排除 singleton schema type,且不会添加 singleton 项目。因此,
S.list().items([...S.documentTypeListItems()])
会移除所有 singleton。请同时使用
.singletons()
:
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'))
组合规则:
  • 所有默认值都可以通过常规链式调用覆盖:
    S.listItem().singleton('siteSettings').title('Global settings')
    、
    S.document().singleton('siteSettings').documentId('override')
    。
  • 系统不会自动区分标题。 列表项 id 默认使用 definition id(根据构造方式保证唯一),但标题会回退到 schema type 的标题,因此共享 schema type 的 singleton 会使用相同标签。请为每个 definition 设置
    title
    ——它会应用在所有呈现 singleton 的位置,包括没有逐项覆盖机制的
    S.list().singletons()
    。
  • S.document().singleton()
    会锁定 singleton 的带 tag 初始值 template,因此即使该 type 存在多个 templates,也会应用其初始值;可使用
    .initialValueTemplate(...)
    覆盖。
  • S.list().singletons()
    返回的 builder 不包含
    .items()
    。
    之后调用
    .items()
    会替换整个数组并静默移除 singleton 项目,因此类型系统禁止这种用法。请先调用
    .items([...])
    ,再调用
    .singletons([...])
    ,或者将
    S.listItem().singleton()
    项目放入常规
    .items()
    数组中。重复调用
    .singletons()
    会追加项目。
  • 不带参数的
    singletons()
    表示所有已注册的 definitions
    (按注册顺序排列,包括插件注册的 definitions);
    singletons([])
    表示不包含任何 singleton。在同一个列表中将其与手写的
    S.listItem().singleton(id)
    混合使用,会重复添加该 singleton,并抛出 duplicate-id
    SerializeError
    ——混合使用时请传入显式数组。
  • S.documentListItem().singleton(id)
    会呈现实时文档预览,而不是静态标题。它的 id 默认使用 definition 的
    documentId
    (而不是 definition id),因为 document list item 的 id 就是它所预览的文档。当 singleton 文档可能尚不存在时,优先使用普通的
    S.listItem()
    。

Shared schema types and the "create new" escape hatch

共享 schema type 与“create new”逃生通道

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,
    {id: 'campaign-non-singleton', title: 'Campaign', schemaType: 'campaign', value: {}},
  ],
},
This is the one place the default structure is not enough on its own: it surfaces the singletons over the shared type, but the type stays filtered, so ordinary documents of it remain unlisted while the structure looks complete. A shared schema type always needs an explicit
S.documentTypeListItem(typeName)
somewhere in the structure. (A future
allowNonSingletonDocuments
flag on the definition will replace both the escape-hatch template and this structure entry.)
The
singleton
tag carries the semantics: templates with it are never create options; templates without it always are. The tag lives on
ResolvedTemplate
— the resolved shape in
source.templates
— not on the authoring
Template
type, so developers never set it directly. To customise a singleton's initial value via
schema.templates
, find the singleton's template by id (equal to the definition id) and spread it (
{...template, value}
) — the spread preserves the tag at runtime, keeping it out of create menus. (Setting
initialValue
on the definition itself is usually simpler.)
为 schema type
T
声明 singleton 后,
T
的普通 per-type template 会被 singleton 的带 tag template 替换,因此
T
会从创建菜单中消失。如果普通
T
文档仍应可创建,需要定义一个显式的未带 tag template——任何 id 均可使用,包括
T
本身,因为普通 template 此时已不存在:
ts
schema: {
  templates: (prev) => [
    ...prev,
    {id: 'campaign-non-singleton', title: 'Campaign', schemaType: 'campaign', value: {}},
  ],
},
这是默认 structure 本身无法完全处理的唯一场景:它会在共享 type 上呈现 singletons,但该 type 仍会被过滤,因此普通文档不会出现在列表中,而 structure 看起来却已经完整。共享的 schema type 始终需要在 structure 中的某个位置显式添加
S.documentTypeListItem(typeName)
。(未来 definition 上的
allowNonSingletonDocuments
flag 将取代逃生通道 template 和此 structure entry。)
singleton
tag 承载以下语义:带有该 tag 的 templates 永远不会成为创建选项,不带该 tag 的 templates 则始终可以创建。该 tag 位于
ResolvedTemplate
上——即
source.templates
中的解析后结构——而不是 authoring
Template
type 上,因此开发者不应直接设置它。若要通过
schema.templates
自定义 singleton 的初始值,请按 id 查找 singleton 的 template(其 id 等于 definition id)并展开它(
{...template, value}
)——展开操作会在运行时保留 tag,使其不出现在创建菜单中。(通常更简单的方法是在 definition 上设置
initialValue
。)

Reading singletons from plugins

从插件中读取 singleton

  • The resolved registry is available at
    source.document.singletons
    (
    useSource().document.singletons
    ).
  • Document-scoped resolvers receive
    context.singleton
    (the definition id;
    documentId
    and
    schemaType
    are already on the context).
  • 已解析的 registry 可通过
    source.document.singletons
    (
    useSource().document.singletons
    )获取。
  • Document-scoped resolvers 会收到
    context.singleton
    (definition id;
    documentId
    和
    schemaType
    已存在于 context 中)。

Migrating a userland singleton setup

迁移 userland singleton 设置

Typical userland implementations combine three pieces; each is subsumed by the registry:
Userland techniqueFirst-class replacement
document.newDocumentOptions
filtering out the type
Automatic (tagged singleton templates are never create options)
document.actions
filtering
duplicate
Automatic (terminal, non-bypassable)
Custom structure with
S.document().schemaType(x).documentId(y)
Keep, or simplify to
S.listItem().singleton(id)
Migration steps:
  1. Add the singleton to
    document.singletons
    (string shorthand when id, document id, and type name are all identical).
  2. Delete the manual
    newDocumentOptions
    and
    duplicate
    filtering for that type.
  3. Replace the manual structure wiring with
    S.listItem().singleton(id)
    (or keep the manual wiring — it remains fully supported; ensure the
    documentId
    /
    schemaType
    pair matches the definition so
    context.singleton
    resolves). If the only reason a custom structure existed was to surface the singletons, delete the resolver entirely and let the default structure do it.
  4. If the schema type is only used by the singleton, nothing else changes. If it's shared with ordinary documents, add an explicit untagged template to keep them creatable, and
    S.documentTypeList(typeName)
    to keep them listed.
典型的 userland 实现通常由三个部分组合而成,而 registry 已涵盖这些功能:
Userland 技术First-class 替代方案
使用
document.newDocumentOptions
过滤该 type
自动处理(带 tag 的 singleton templates 永远不会成为创建选项)
使用
document.actions
过滤
duplicate
自动处理(终态且无法绕过)
使用
S.document().schemaType(x).documentId(y)
的自定义 structure
保留,或简化为
S.listItem().singleton(id)
迁移步骤:
  1. 将 singleton 添加到
    document.singletons
    (当 id、document id 和 type name 完全相同时可使用字符串 shorthand)。
  2. 删除针对该 type 的手动
    newDocumentOptions
    和
    duplicate
    过滤逻辑。
  3. 使用
    S.listItem().singleton(id)
    替换手动 structure 接线(也可以保留手动接线——它仍完全受支持;请确保
    documentId
    /
    schemaType
    组合与 definition 匹配,以便
    context.singleton
    能够正确解析)。如果自定义 structure 存在的唯一原因是呈现 singletons,请删除整个 resolver,让默认 structure 负责处理。
  4. 如果该 schema type 仅供 singleton 使用,则无需进行其他更改。如果它与普通文档共享,请添加显式的未带 tag template 以保持其可创建性,并添加
    S.documentTypeList(typeName)
    以保持其出现在列表中。

Migrating from
sanity-plugin-singleton-management

从
sanity-plugin-singleton-management
迁移

sanity-plugin-singleton-management
(a maintained fork of
sanity-plugin-singleton-tools
, with an identical API) marks schema types with
options: {singleton: true}
, registers a
singletonTools()
plugin that filters new-document options and document actions, and ships three structure helpers. Every piece maps directly onto the first-class API:
Plugin techniqueFirst-class replacement
options: {singleton: true}
on the schema type
A
document.singletons
entry (delete the schema option — it has no first-class meaning)
singletonTools()
plugin in
plugins: [...]
Delete (creation and duplicate prevention are automatic)
singletonDocumentListItem({S, context, type, title, id, icon})
S.listItem().singleton(<definition id>)
(title/icon defaults come from the definition, then the schema type)
singletonDocumentListItems({S, context})
S.list().singletons()
(or nothing at all, if using the default structure)
filteredDocumentListItems({S, context})
...S.documentTypeListItems()
— singleton schema types are excluded automatically
Document id compatibility (the critical detail): the plugin's document id defaults to the schema type name (
S.document().schemaType(type).id(id ?? type)
), so a plugin singleton
mySingleton
edits the document
mySingleton
. The string shorthand (
singletons: ['mySingleton']
) produces exactly that document id — existing content keeps working with no data migration. If the plugin call passed an explicit
id
, carry it over as the definition's
documentId
.
Migration steps:
  1. For each schema type with
    options: {singleton: true}
    : remove the option, and register the type in
    document.singletons
    — the string shorthand when the plugin used the default id, otherwise
    {documentId: <the explicit id>, schemaType: <type>}
    .
  2. Remove
    singletonTools()
    from
    plugins
    and uninstall the package.
  3. Replace the structure helpers per the table above. The plugin passed
    {S, context}
    around; the first-class helpers need neither — they read the registry from the builder's own context.
  4. Note a deliberate behavioural difference: the plugin allowlists actions (
    publish
    ,
    discardChanges
    ,
    restore
    ), hiding delete and unpublish. The first-class API only removes
    duplicate
    — deleting a singleton is a legitimate "reset". To preserve the plugin's stricter behaviour, add:
    ts
    document: {
      actions: (prev, context) =>
        context.singleton
          ? prev.filter(({action}) =>
              ['publish', 'discardChanges', 'restore'].includes(action as string),
            )
          : prev,
    },
    Only add this if the stricter behaviour is genuinely wanted — otherwise prefer the first-class default.
  5. If the plugin's
    singletonDocumentListItem
    was used to render multiple singletons of the same schema type (its
    title
    /
    id
    overrides), register one definition per document id —
    {documentId, schemaType, title}
    — which is the first-class model for exactly that case.
sanity-plugin-singleton-management
(
sanity-plugin-singleton-tools
的维护分支,API 完全相同)通过
options: {singleton: true}
标记 schema types,注册一个用于过滤 new-document options 和 document actions 的
singletonTools()
插件,并提供三个 structure helpers。每一部分都可以直接映射到 first-class API:
插件技术First-class 替代方案
schema type 上的
options: {singleton: true}
document.singletons
entry(删除 schema option——它在 first-class API 中没有意义)
plugins: [...]
中的
singletonTools()
插件
删除(创建和防重复功能会自动生效)
singletonDocumentListItem({S, context, type, title, id, icon})
S.listItem().singleton(<definition id>)
(title/icon 默认值依次来自 definition 和 schema type)
singletonDocumentListItems({S, context})
S.list().singletons()
(如果使用默认 structure,则无需任何操作)
filteredDocumentListItems({S, context})
...S.documentTypeListItems()
——singleton schema types 会被自动排除
Document id 兼容性(关键细节): 该插件的 document id 默认使用 schema type 名称(
S.document().schemaType(type).id(id ?? type)
),因此 plugin singleton
mySingleton
编辑的文档就是
mySingleton
。字符串 shorthand(
singletons: ['mySingleton']
)会生成完全相同的 document id,因此现有内容无需数据迁移即可继续使用。如果插件调用时传入了显式
id
,则应将其迁移为 definition 的
documentId
。
迁移步骤:
  1. 对于每个设置了
    options: {singleton: true}
    的 schema type:删除该 option,并将该 type 注册到
    document.singletons
    ——插件使用默认 id 时使用字符串 shorthand,否则使用
    {documentId: <the explicit id>, schemaType: <type>}
    。
  2. 从
    plugins
    中移除
    singletonTools()
    并卸载该软件包。
  3. 按照上表替换 structure helpers。该插件会在各处传递
    {S, context}
    ;first-class helpers 两者都不需要——它们会从 builder 自身的 context 中读取 registry。
  4. 请注意一个有意保留的行为差异:该插件通过 allowlist 限定 actions(
    publish
    、
    discardChanges
    、
    restore
    ),因此会隐藏 delete 和 unpublish。first-class API 只会移除
    duplicate
    ——删除 singleton 是一种合法的“重置”。如需保留插件更严格的行为,请添加:
    ts
    document: {
      actions: (prev, context) =>
        context.singleton
          ? prev.filter(({action}) =>
              ['publish', 'discardChanges', 'restore'].includes(action as string),
            )
          : prev,
    },
    仅当确实需要这种更严格的行为时才应添加;否则建议使用 first-class 默认行为。
  5. 如果曾使用
    singletonDocumentListItem
    呈现同一 schema type 的多个 singletons(并覆盖其
    title
    /
    id
    ),请为每个 document id 注册一个 definition——
    {documentId, schemaType, title}
    ——这正是 first-class 模型用于处理此类场景的方式。

Reference

参考

Source lives in the
sanity-io/sanity
monorepo:
源代码位于
sanity-io/sanity
monorepo: