How AI is powering better personalization in e-commerce [with Vercel]. Sign up now

Implementing Singleton Document Internationalization in Sanity Structure

8 repliesLast updated: Dec 1, 2025

Help me implement this: https://github.com/sanity-io/document-internationalization/blob/main/docs/known-caveats.md#usage-with-singletons
If I add that code to

export default () => S.list()
  .id('__root__')
  .title('Content')
  ..items([
    [add here?], <---- Throws error! Expected List item, found Object
    S.divider(),
    ...restOfTypes
  ])
AI Update

The URL you're trying to follow (known-caveats.md) doesn't actually exist in the document-internationalization repository—it returns a 404 error. However, the actual documentation for implementing singletons is in 01-singleton-documents.md.

The error "Expected List item, found Object" happens because you're trying to spread a single object directly into the items array. The fix is to use the spread operator (...) on the array returned by .map(), not on individual objects.

Here's the correct implementation from the actual singleton documentation:

import {StructureResolver} from 'sanity/structure'

// Define your singleton documents
const SINGLETONS = [
  {id: 'home', _type: 'page', title: 'Home'},
  {id: 'login', _type: 'page', title: 'Login'},
]

// Define your languages
const LANGUAGES = [
  {id: 'en', title: 'English'},
  {id: 'fr', title: 'French'},
]

export const structure: StructureResolver = (S) =>
  S.list()
    .title('Content')
    .items([
      // Spread the array returned by .map() - this is the key!
      ...SINGLETONS.map((singleton) =>
        S.listItem()
          .title(singleton.title)
          .id(singleton.id)
          .child(
            S.list()
              .title(singleton.title)
              .id(singleton.id)
              .items(
                LANGUAGES.map((language) =>
                  S.documentListItem()
                    .schemaType('page')
                    .id(`${singleton.id}-${language.id}`)
                    .title(`${singleton.title} (${language.id.toLocaleUpperCase()})`)
                )
              )
              .canHandleIntent(
                (intentName, params) => 
                  intentName === 'edit' && params.id.startsWith(singleton.id)
              )
          )
      ),
      S.divider(),
      // ...other list items
    ])

Why this works:

Before this will work, you need to:

defineField({
  name: 'language',
  type: 'string',
  readOnly: true,
  hidden: true,
})

The canHandleIntent ensures that when you click on a singleton document from elsewhere in the Studio, it opens in the correct place in your structure.

Show original thread
8 replies

Was this answer helpful?

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Related contributions