Studio loses track of path when creating new document with custom deskStructure

6 replies
Last updated: Nov 30, 2021
Hi, I have a weird issue where the studio seems to loose track of the “path” I’m in.
I’m using a custom deskStructure to allow for multilingual content and this weird jump happens whenever I’m creating a new document (see video):

(Please also see structure in comment)
AI Update

This is a classic issue with custom desk structures in Sanity Studio! When you create a new document and the Studio "loses track" of where you are (jumping back to the root or wrong pane), it's almost always because your custom structure is missing the canHandleIntent method on your list items.

The Problem

When you create a new document, Sanity Studio tries to figure out which pane should handle that intent (like "create" or "edit"). Without canHandleIntent defined, the Studio can't match the new document to your custom filtered list items, so it falls back to opening the document in an unexpected location—usually the root pane.

The Solution

You need to add the canHandleIntent method to your custom listItem() definitions. This tells the Studio "yes, this pane can handle documents of this type with these characteristics."

Here's an example for a multilingual structure:

S.listItem()
  .title('English Content')
  .id('english-content')
  .child(
    S.documentList()
      .title('English Content')
      .filter('_type == $type && language == $language')
      .params({ type: 'page', language: 'en' })
  )
  .canHandleIntent((intentName, params) => {
    // Handle create and edit intents for documents with English language
    return (
      (intentName === 'create' || intentName === 'edit') &&
      params.type === 'page' &&
      params.language === 'en'
    )
  })

Key Points for Language-Based Structures

For multilingual setups specifically, your canHandleIntent should check:

  1. Intent type - usually 'create' or 'edit'
  2. Schema type - the document type being created/edited (available as params.type)
  3. Language parameter - match it to your filtered list (available as params.language if you're setting it via initial value templates)

If you're using language folders, each language-specific list item needs its own canHandleIntent that checks for matching language values.

Complete Pattern

Here's a more complete pattern for a language-based structure:

const languageList = (lang: string) =>
  S.listItem()
    .title(`${lang.toUpperCase()} Content`)
    .id(`${lang}-content`)
    .child(
      S.documentList()
        .title(`${lang.toUpperCase()} Content`)
        .filter('_type in $types && language == $lang')
        .params({ types: ['page', 'post'], lang })
    )
    .canHandleIntent((intentName, params) => {
      return (
        ['create', 'edit'].includes(intentName) &&
        params.language === lang
      )
    })

export const structure: StructureResolver = (S) =>
  S.list()
    .title('Content')
    .items([
      languageList('en'),
      languageList('es'),
      languageList('fr'),
      // ...other language lists
    ])

Important Notes

  • Make sure the parameters you check in canHandleIntent match what's actually on your documents or what's being passed through initial value templates
  • If you're not setting a language field when creating documents, you may need to use initial value templates that set the language parameter
  • The id() on your list items is also important—make sure each has a unique, descriptive ID

Debugging Tips

If it's still not working after adding canHandleIntent:

  1. Check your initial value templates to ensure they're passing the correct parameters (especially language)
  2. Console log the params object inside canHandleIntent to see what's actually being passed
  3. Verify that your document schema has a language field that matches what you're filtering on

The Structure Builder API reference has more details on all available methods. The canHandleIntent method is documented under both List and ComponentView sections, though examples can be tricky to find since it's often overlooked until you hit this exact issue!

Show original thread
6 replies
Here’s the relevant part of the structure:
["documentReference", "documentPerson"].map((doc) =>
    S.documentTypeListItem(doc)
      .title(doc.replace("document", ""))
      .id(doc)
      .child(() =>
        S.list()
          .title("Locales")
          .id("locale")
          .items([
            ...locales.map(({ title, code, emoji }) =>
              S.documentTypeListItem(doc)
                .id(code)
                .title(title)
                .icon(renderEmoji(emoji))
                .child(
                  S.documentTypeList(doc)
                    .id("id")
                    .title(`${doc.replace("document", "")} (${title})`)
                    .filter(`_type == "${doc}" && i18n.locale == "${code}"`)
                    .child(
                      S.document()
                        .schemaType(doc)
                        .views([S.view.form(), ...preview])
                    )
                )
            ),
            S.divider(),
            S.documentTypeListItem(doc)
              .title("Undefined")
              .icon(renderEmoji("❔"))
              .child(
                S.documentTypeList(doc)
                  .id("id")
                  .filter(`_type == "${doc}" && !defined(i18n.locale)`)
                  .child(
                    S.document()
                      .schemaType(doc)
                      .views([S.view.form(), ...preview])
                  )
              ),
          ])
      )
  );

locales
is an array of locales I’ve defined in a config file:
"locales": [
      { "title": "English", "code": "en", "emoji": "🇬🇧" },
      { "title": "Deutsch", "code": "de", "emoji": "🇩🇪" }
    ]
What version of the Studio are you running?
Was running 2.22.0, now upgraded to the most current (2.22.2)
I believe those issues were resolved with a release last week that changed how intent resolvers were handled. Can you try adding an intent handler to your structure? It would look something like this:
S.listItem()
  .title('Title')
  .schemaType(<schema-type>)
  .child(() =>
     S.documentList()
       .schemaType(<schema-type>)
       .title('Title')
       .canHandleIntent(() => S.documentTypeList(<schema-type>).getCanHandleIntent())
       .child(
         //child
        )
   ),
Great, this seems to work! Thanks a lot

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.

Was this answer helpful?