> For AI agents: the complete Sanity documentation index is available at [https://www.sanity.io/docs/llms.txt](https://www.sanity.io/docs/llms.txt).

# Handle intents in the Structure Tool

How the Structure Tool matches intents to panes, and how to declare intent handling on lists that don't match on their own.

Intents are Studio's internal routing mechanism. When a user clicks a search result, follows an **Open in Studio** link from Visual Editing, or uses a **Create new** button, Studio fires an intent (like `edit` or `create`) with parameters such as the document ID and type. The Structure Tool resolves that intent by finding the right pane in your structure.

This guide explains how Studio decides which pane handles an intent, and shows how to declare intent handling on lists that don't match on their own.

## Prerequisites

- A studio with the Structure Tool installed. New projects include it; for existing projects, [install it by updating your project's configuration file](https://www.sanity.io/docs/studio/structure-tool).
- Familiarity with panes, lists, and child resolvers, as covered in [Structure tool and Structure builder](https://www.sanity.io/docs/studio/structure-introduction).

## How Studio matches an intent to a pane

Studio matches an intent to a pane in two independent ways. Either one is enough.

First, a pane can declare what it handles with `canHandleIntent`. Lists built with `S.documentTypeList()` and `S.documentTypeListItem()` get a default implementation that reads the schema types named in the list's filter and matches when the intent's `type` parameter is one of them. Any filter that names a type counts, such as `_type == "post" && defined(publishedAt)` — not only the default filter.

Second, the intent resolver checks the pane directly, whatever its `canHandleIntent` says. A `documentList` matches when its schema type equals the intent's `type` parameter and its filter is still exactly `_type == $type`.

Panes that match neither way need `canHandleIntent` to declare which intents they handle: a custom `S.list()`, or a `documentList` whose filter doesn't name a schema type.

Adding a custom `.child()` resolver to a `documentTypeList` clears the default `canHandleIntent`, because Studio can't guarantee the new child handles the intent. The list keeps matching through the second route anyway, since `.child()` changes neither the schema type nor the filter. Change the filter as well and both routes drop out, so you have to declare `canHandleIntent` yourself.

What `.child()` does change is what opens. The intent routes to a pane for the target document ID, and your resolver decides what that pane shows. If it returns a list instead of a document node, the document editor doesn't open, and `canHandleIntent` won't change that.

## Common symptoms of missing intent handling

If a list in your structure matches neither way, and you notice any of these, missing `canHandleIntent` could be the cause:

- **Documents open in the wrong pane.** The document opens as a bare editor instead of navigating to the correct location in your structure.
- **Open in Studio links from Visual Editing don't route correctly.**
- **Search results land in the wrong place.** Global search can bypass your custom structure entirely.
- **Create new buttons may not work as expected.** Custom structures that replace `documentTypeList` can lose the built-in create intent handling. A `documentTypeList` that keeps its default filter and adds a `.child()` resolver has a different cause: the intent routes correctly, but the child resolver decides what opens.

When no pane in your structure matches, Studio opens the document in a fallback editor outside your structure. You can recognize it by the pane ID in the URL, which starts with `__edit__`.

## Add intent handling

Add `canHandleIntent` to any list that matches neither way. The function receives the intent name and parameters, and returns `true` if the pane should handle the intent:

**structure.ts**

```typescript
// structure.ts
import type {StructureResolver} from 'sanity/structure'

export const structure: StructureResolver = (S) =>
  S.list()
    .title('Content')
    .items([
      // Built-in documentTypeList: intent handling works automatically
      S.documentTypeListItem('author').title('Authors'),

      // Custom child resolver: needs canHandleIntent
      S.listItem()
        .title('Blog Posts')
        .schemaType('post')
        .child(
          S.documentTypeList('post')
            .title('Blog Posts')
            .child((documentId) =>
              S.document()
                .documentId(documentId)
                .schemaType('post')
            )
            .canHandleIntent((intentName, params) =>
              ['create', 'edit'].includes(intentName) && params.type === 'post'
            )
        ),
    ])
```

> [!WARNING]
> Custom lists need intent handling
> A custom `S.list()`, or a `documentList` whose filter doesn't name a schema type, matches no intents by default. Without `canHandleIntent`, search results, Visual Editing links, and **Create new** buttons open the fallback editor instead of your structure.

For the full `canHandleIntent` signature and parameters, see the [Structure Builder API Reference](https://www.sanity.io/docs/studio/structure-builder-reference).

## Next steps

- [Create Studio edit intent links](https://www.sanity.io/docs/visual-editing/studio-edit-intent-links): build the URLs that fire an `edit` intent, including the ones the Vision Tool renders next to `_id` and `_ref` values.
- [Get started with Structure Builder API](https://www.sanity.io/docs/studio/structure-builder-introduction): build the lists, panes, and child resolvers that intent handling applies to.

