👀 See Sanity in action: Watch product demo now →
January 16, 2023

Create a Singleton Document Type in Sanity V3

By Corey Ward

This guide will go over the three APIs you have to configure to create a singleton document type in the Sanity Studio in v3. This includes preventing users from creating new instances from the global creation menu, showing the document in the desk structure without a list, and removing document actions like “duplicate” and “delete” that can cause issues.

Configuring these areas requires three separate APIs/features, but conveniently, all of them can be configured in the sanity.config.js file.

Here is a simple but complete example showing the configuration for a singleton settings document:

import { defineConfig } from "sanity"
import { deskTool } from "sanity/desk"
import { visionTool } from "@sanity/vision"
import { schemaTypes } from "./schemas"

// Define the actions that should be available for singleton documents
const singletonActions = new Set(["publish", "discardChanges", "restore"])

// Define the singleton document types
const singletonTypes = new Set(["settings"])

export default defineConfig({
  name: "default",
  title: "Your Awesome CMS",

  projectId: "xxxyyyzzz",
  dataset: "production",

  plugins: [
    deskTool({
      structure: (S) =>
        S.list()
          .title("Content")
          .items([

            // Our singleton type has a list item with a custom child
            S.listItem()
              .title("Settings")
              .id("settings")
              .child(
                // Instead of rendering a list of documents, we render a single
                // document, specifying the `documentId` manually to ensure
                // that we're editing the single instance of the document
                S.document()
                  .schemaType("settings")
                  .documentId("settings")
              ),

            // Regular document types
            S.documentTypeListItem("blogPost").title("Blog Posts"),
            S.documentTypeListItem("author").title("Authors"),
          ]),
    }),
    visionTool(),
  ],

  schema: {
    types: schemaTypes,

    // Filter out singleton types from the global “New document” menu options
    templates: (templates) =>
      templates.filter(({ schemaType }) => !singletonTypes.has(schemaType)),
  },

  document: {
    // For singleton types, filter out actions that are not explicitly included
    // in the `singletonActions` list defined above
    actions: (input, context) =>
      singletonTypes.has(context.schemaType)
        ? input.filter(({ action }) => action && singletonActions.has(action))
        : input,
  },
})

Gotcha

Since this process disables the UI for creating a new instance of your singleton document, you'll want to create the document before adding it to the singletonTypes set.

Step 1: Setup

Defining the actions for singleton documents

// Define the actions that should be available for singleton documents
const singletonActions = new Set(["publish", "discardChanges", "restore"])

The first step in configuring a singleton document type is to define the actions that should be available for singleton documents. This is done by creating a new Set and adding the desired actions as strings. In the provided example, the singletonActions set includes the actions "publish", "discardChanges", and "restore". If you have customized your document actions, you may want to add one or more of your custom actions to this list, too.

Defining the singleton document types

// Define the singleton document types
const singletonTypes = new Set(["settings"])

The next step is to define which document types should be treated as singletons. This is done by creating a new set and adding the desired document types as strings. In the provided example, the singletonTypes set includes the document type "settings". If you have more than one type of document that should be treated as a singleton, you would add each of them here.

Step 2: Configure the desk structure

The Sanity Studio's "desk" is the main interface for editing documents, and it's necessary to configure it to properly display singleton document types. This is done using the deskTool function and the structure property. In the provided example, the listItem child is being passed a document editor view configured with the documentId manually to control the document shown when an editor clicks on the “Settings” list item.

 // Our singleton type has a list item with a custom child
S.listItem()
  .title("Settings")
  .id("settings")
  .child(
    // Instead of rendering a list of documents, we render a single
    // document, specifying the `documentId` manually to ensure
    // that we're editing the single instance of the document
    S.document()
      .schemaType("settings")
      .documentId("settings")
  )

For more on how this works, see the Structure Builder Reference documentation.

Step 3: Hiding singleton types from the global “New document” menu

To prevent users from creating new instances of singleton document types from the global creation menu, the templates property is used to filter out the singleton types. In the provided example, the templates property is passed a callback function that filters out any templates with a schemaType that matches an item in the singletonTypes set.

Learn more about this part of the Initial Value Templates API here.

Step 4: Hiding unwanted actions for singleton types

Finally, to remove document actions such as "duplicate" and "delete" that may cause issues with singleton document types, the document property is used to filter out actions that are not explicitly included in the singletonActions set. In the provided example, the actions property is passed a callback function that filters out any actions that are not in the singletonActions set for documents with a schemaType that matches an item in the singletonTypes set.

Final thoughts

That's all that is required to create singleton document types in Sanity Studio v3! Setting up singletonActions and singletonTypes up front makes the process of adding new singleton type documents easy, but as a bonus, a simple function can be used to abstract the listItem definition to reduce verbosity!

const singletonListItem = (
  S: StructureBuilder,
  typeName: string,
  title?: string
) =>
  S.listItem()
    .title(title || typeName)
    .id(typeName)
    .child(S.document().schemaType(typeName).documentId(typeName))
    
    
// example usage:
S.list()
  .items([
    singletonListItem(S, "settings", "Settings")
      .icon(RiSettingsLine),
  ])