--- url: /introduction/getting-started.md --- # Getting Started {#getting-started} **Sanity Structure Tool** is a declarative, JSON based plugin for defining your Sanity Studio's desk structure. This guide provides a step-by-step roadmap to help you install the package and configure a maintainable content hierarchy. ## Installation {#installation} Install the package using your preferred package manager. ::: code-group ```sh [npm] npm install sanity-plugin-structure-tool ``` ```sh [yarn] yarn add sanity-plugin-structure-tool ``` ```sh [pnpm] pnpm add sanity-plugin-structure-tool ``` ```sh [bun] bun add sanity-plugin-structure-tool ``` ::: ## Quick Roadmap {#roadmap} Once installed, follow these steps to get your studio up and running with a declarative structure: 1. **[Follow the Setup Guide](/guide/setup)**: Learn how to configure the plugin and register it. 2. **[Define Your Items](/guide/list-items)**: Explore the `ListItem` configuration to build your hierarchy. 3. **[Browse Examples](/examples/title)**: See specific examples for properties like `singleton`, `filter`, and `children`. ## Roadmap to v1.0.0 {#roadmap-to-v-1-0-0} We are actively working towards our stable **v1.0.0** release. This milestone will include key features like custom components, enhanced ordering, view customization, and robust TypeScript validation. Check out the full **[Upcoming Features & Roadmap](/introduction/upcoming-features)** to see what's planned. ## Learn More {#learn-more} * **[Why this tool?](/introduction/why)**: Understand the core philosophy and the problems it solves. * **[FAQ](/guide/faq)**: Find answers to common questions and troubleshooting tips. * **[Contributing](/contribute/guide)**: Learn how to contribute to the project. ## Need Help? {#need-help} If you run into issues or have questions: * Check the [Issues](https://github.com/sanity-plugin/structure-tool/issues) on GitHub. * Contributions are always welcome! --- --- url: /introduction/why.md --- # Why Sanity Structure Tool? {#why-sanity-structure-tool} Sanity Studio's native `Structure Builder` is incredibly powerful, but as projects grow, building complex, nested structures with it can become verbose, repetitive, and difficult to maintain. **Sanity Structure Tool** is designed to simplify this process by providing a **declarative, JSON-based API** or type-safe **helpers** for defining your studio's structure. ## The Problem {#the-problem} When using the standard Structure Builder, you often find yourself writing a lot of boilerplate code for common tasks: * **Singletons:** Manually creating a list item that opens a specific document ID and hiding it from the main list. * **Pluralization:** Repeatedly defining titles and icons for document types. * **Nesting:** Managing deeply nested lists with multiple `S.list().title().items([...])` calls. * **Workspace-based Content:** Implementing conditional logic to hide or show content based on workspaces. * **Role-based Access:** Implementing conditional logic to hide or show items based on user roles. This imperative approach can lead to a structure file that is hard to read and even harder to refactor. ## The Solution {#the-solution} Sanity Structure Tool allows you to define your structure as a clean, hierarchical data structure using either plain JSON-based objects or expressive, type-safe **helpers**. ### 1. Declarative Syntax {#declarative-syntax} Instead of chaining methods, you define what you want your structure to look like. This makes it easier to visualize the hierarchy and understand the layout at a glance. ### 2. Built-in Singleton Support {#built-in-singleton-support} Creating a singleton is as simple as adding `singleton: true` to your JSON configuration, or using `helpers.singleton('...')`. The plugin handles the document ID generation, the editor view, and the filtering automatically. ### 3. Automatic Pluralization {#automatic-pluralization} By default, the plugin uses `pluralize` to generate titles for your document lists, but you can easily override this or disable it per item. ### 4. Workspace & Role Awareness {#workspace-role-awareness} The tool is built with multi-role and multi-workspace environments in mind. You can easily restrict visibility of specific list items based on the workspace or the current user's roles using JSON configuration properties or helpers. ### 5. Developer Experience (DX) {#developer-experience-dx} With full TypeScript support, you get autocompletion and type safety for your structure definitions, reducing the risk of runtime errors and making the development process much smoother. ### 6. Escape Hatch {#escape-hatch} Need to do something highly custom that the declarative API doesn't support? You can use the `raw` property in your JSON configuration or `helpers.raw` to drop back into the standard Sanity Structure Builder whenever you need to. ## Conclusion {#conclusion} Sanity Structure Tool doesn't replace the Structure Builder, it builds on top of it to offer a more maintainable and developer-friendly way to manage your Sanity Studio's desk structure. --- --- url: /introduction/upcoming-features.md --- # Upcoming Features {#upcoming-features} We are constantly working to expand the capabilities of **Sanity Structure Tool** to make it the most powerful and flexible way to define your Sanity Studio structure via JSON. Here is a glimpse of what's coming in future releases: ## Planned Features {#planned-features} ### 1. Bulk Actions (Delete All) {#delete-all} Support for automatic bulk deletion. When enabled via the `deleteAll` property, the plugin will automatically handle both rendering a delete trigger in the UI and executing the document deletion queries for all matching items in the listing. ::: code-group ```ts [JSON] { schemaType: 'draftPost', deleteAll: ({ currentUser }) => currentUser.roles.some((role) => role.name === 'administrator'), } ``` ```ts [Helpers] helpers.listing('draftPost', { deleteAll: ({ currentUser }) => currentUser.roles.some((role) => role.name === 'administrator'), }); ``` ::: ### 2. Custom List Item Badges {#item-badges} Support for adding custom status badges or counters next to list items to highlight specific states (e.g. showing the count of pending documents or warning tags). ::: code-group ```ts [JSON] { schemaType: 'post', badge: { title: 'Draft', color: 'warning', query: '_id in path("drafts.**")', }, } ``` ```ts [Helpers] helpers.listing('post', { badge: { title: 'Draft', color: 'warning', query: '_id in path("drafts.**")', }, }); ``` ::: ### 3. Dynamic Icon Callbacks {#dynamic-icons} Support for resolving list item icons dynamically using callback functions. This will allow rendering different icons based on active workspaces, current user roles, or custom metadata values. ::: code-group ```ts [JSON] { schemaType: 'post', icon: ({ workspace }) => (workspace === 'canary' ? StarIcon : DocumentIcon), } ``` ```ts [Helpers] helpers.listing('post', { icon: ({ workspace }) => (workspace === 'canary' ? StarIcon : DocumentIcon), }); ``` ::: *** ::: info Have a Suggestion? Is there something else you'd like to see before we hit v2.0.0? We'd love to hear your feedback! Feel free to open an issue or start a discussion on our [GitHub repository](https://github.com/sanity-plugin/structure-tool/discussions/new/choose). ::: --- --- url: /guide/setup.md --- # Overview {#overview} After [installing](/introduction/getting-started#installation) the package, follow these three simple steps to integrate **Sanity Structure Tool** into your studio. ## 1. Configuration {#configuration} First, create a file to configure the plugin. This generates the typed utilities and helpers you'll use throughout your project. ::: code-group ```ts [src/structure/index.ts] import { structureToolPlugin } from 'sanity-plugin-structure-tool'; export const { structure, defineListItems, helpers } = structureToolPlugin({ title: 'My Project', }); ``` ::: ::: info Advanced Configuration For dynamic titles, custom roles, or workspace support, see the full **[Configuration Guide](./setup/configuration)**. ::: ## 2. Define List Items {#define-list-items} Next, define your studio's desk hierarchy in a separate file. You can use either the `helpers` or raw objects with `defineListItems`. ::: code-group ```ts [JSON] // src/structure/listItems.ts import { defineListItems } from './index'; const listItems = defineListItems([ { schemaType: 'author', }, { title: 'Settings', schemaType: 'settings', singleton: true, }, ]); export default listItems; ``` ```ts [Helpers] // src/structure/listItems.ts import { defineListItems } from './index'; const listItems = defineListItems([ helpers.listing('author'), helpers.singleton('settings', { title: 'Settings', }), ]; export default listItems; ``` ::: ::: tip Advanced Usage Learn more about type safety and modular items in the **[Define List Items Guide](./setup/define-list-items)**. ::: ## 3. Register the Plugin {#step-3-register-plugin} Finally, add the `structure` plugin and the `SingletonAction` to your `sanity.config.ts`. ::: code-group ```ts [sanity.config.ts] import { defineConfig } from 'sanity'; import { SingletonAction } from 'sanity-plugin-structure-tool'; import { structure } from './src/structure'; import listItems from './src/structure/listItems'; export default defineConfig({ // ... your studio configuration plugins: [ structure({ listItems, }), ], document: { // Required to handle document actions for singletons actions: SingletonAction, }, }); ``` ::: ::: info What is SingletonAction? The `SingletonAction` is essential for singletons to work correctly. It ensures that document actions like "Delete" or "Duplicate" are hidden for singletons, while preserving them for regular documents. Learn more in the **[Singleton Action Guide](../customization/singleton-action)**. ::: ## Verification {#verification} To confirm everything is working as expected: 1. Start your studio: ::: code-group ```sh [npm] npm run dev ``` ```sh [yarn] yarn dev ``` ```sh [pnpm] pnpm dev ``` ```sh [bun] bun dev ``` ::: 2. Navigate to the **Structure** tab in your browser. 3. You should see your list items (e.g., "Authors" and "Settings") rendered correctly. ## Next Steps {#next-steps} Now that your base setup is complete, explore more: * **[List Items](./list-items)**: Learn how to add icons, filters, and custom parameters. * **[Helpers](./helpers)**: Learn about the built-in functions to define your structure. * **[Examples](../examples/title)**: See specific examples for each field. * **[FAQ](./faq)**: Find answers to common questions. --- --- url: /guide/setup/configuration.md --- # Configuration {#configuration} The first step in using **Sanity Structure Tool** is configuring the plugin. Instead of importing directly from the package, we recommend creating a dedicated file (e.g., `src/structure/index.ts`) to generate **typed utilities** specific to your project. ## Basic Example {#basic-example} A minimal setup with just a static title. ::: code-group ```ts [src/structure/index.ts] import { structureToolPlugin } from 'sanity-plugin-structure-tool'; export const { structure, defineListItems, helpers } = structureToolPlugin({ title: 'Project Name', }); ``` ::: ## Advanced Example {#advanced-example} A complete setup utilizing dynamic titles, custom roles, and workspaces for enhanced type safety and access control. ::: code-group ```ts [src/structure/index.ts] import { structureToolPlugin } from 'sanity-plugin-structure-tool'; export const { structure, defineListItems, helpers } = structureToolPlugin({ // Dynamic title based on active workspace title: ({ workspace }) => `${workspace} Workspace`, // Custom title for empty lists emptyListTitle: 'No items configured', // Define available workspaces workspaces: ['workspace-1', 'workspace-2'], defaultWorkspaces: ['workspace-1'], // Define available user roles roles: ['administrator', 'editor', 'viewer'], defaultRoles: ['administrator'], }); ``` ::: ## Parameters {#parameters} The `structureToolPlugin` function accepts a configuration object with the following properties: ### `defaultRoles` {#default-roles} * **Type**: `readonly string[]` * **Optional**: Yes (Required if `roles` is provided) The baseline roles for all items. ```ts defaultRoles: ['administrator'], ``` ### `defaultWorkspaces` {#default-workspaces} * **Type**: `readonly string[]` * **Optional**: Yes (Required if `workspaces` is provided) The baseline workspaces for all items. ```ts defaultWorkspaces: ['workspace-1'], ``` ### `emptyListTitle` {#empty-list-title} * **Type**: `string | ((params: { workspace: string, currentUser: CurrentUser, context: ConfigContext }) => string)` * **Optional**: Yes The title shown when a document list has no items configured. ```ts emptyListTitle: 'Nothing to see here', ``` ### `enableAutoGenerateTemplates` {#enable-auto-generate-templates} * **Type**: `boolean` * **Optional**: Yes (Default: `true`) Controls whether the plugin automatically generates initial value templates for singletons or listing documents. ::: warning Limitations with `childOptions` Auto-generation of templates **will not work** for list items that dynamically resolve their `schemaType`, `children`, or `templates` properties using `childOptions` in callback functions. Because initial value templates are registered globally at schema compilation time outside of a desk route path, `childOptions` context is not available. If you rely on `childOptions` for these properties, you must set `enableAutoGenerateTemplates: false` and register/configure your templates manually. ::: ```ts enableAutoGenerateTemplates: false, ``` ### `i18n` {#i18n} * **Type**: `Record>` * **Optional**: Yes Locale resource bundles mapping custom translation keys for each translation locale code. For more information, see the [Internationalization (i18n) Setup](./i18n) guide. ### `roles` {#roles} * **Type**: `readonly string[]` * **Optional**: Yes An array of all user roles. Enabling this allows you to use the `roles` property in `ListItem`. ```ts roles: ['administrator', 'editor', 'viewer'], ``` ### `title` {#title} * **Type**: `string | ((params: { workspace: string, currentUser: CurrentUser, context: ConfigContext }) => string)` * **Required**: Yes The title of the structure in the Sanity desk. ```ts title: 'Project Name', ``` ### `workspaces` {#workspaces} * **Type**: `readonly string[]` * **Optional**: Yes An array of all workspace names. Enabling this allows you to use the `workspaces` property in `ListItem`. ```ts workspaces: ['workspace-1', 'workspace-2'], ``` ## Returns {#returns} The `structureToolPlugin` function returns a set of utilities that are internally bound to your configuration (including custom types for workspaces and roles). ### `structure` {#return-structure} The core plugin utility. This is what you import and use within the `plugins` array of your `sanity.config.ts`. ::: code-group ```ts [sanity.config.ts] import { structure } from './src/structure'; export default defineConfig({ plugins: [ structure({ listItems, }), ], }); ``` ::: ### `helpers` {#return-helpers} A set of helper functions (`listing`, `singleton`, `divider`, etc.) that are used to define your structure with full type safety. [Read the full guide on helpers](../helpers). ### `defineListItems` {#return-define-list-items} A helper used to define the entire hierarchy of your Sanity desk. It ensures that the array of items you provide follows the `ListItem` schema and utilizes any `workspaces/roles` defined during initialization. [Read the full guide on defining list items](./define-list-items). ### `defineListItem` {#return-define-list-item} Similar to `defineListItems`, but used for defining a single `ListItem`. This is particularly useful when you want to create modular sections or reuse items across different lists. ::: code-group ```ts [JSON] export const shopSection = defineListItem({ title: 'Shop', children: [ { schemaType: 'product', }, { schemaType: 'category', }, { title: 'Sales Information', isDivider: true, }, { schemaType: 'discount', }, ], }); ``` ```ts [Helpers] export const shopSection = helpers.children('Shop', [ helpers.listing('product'), helpers.listing('category'), helpers.divider('Sales Information'), helpers.listing('discount'), ]); ``` ::: ### `templates` {#return-templates} A utility for registering **Initial Value Templates**. [Initial value templates](https://www.sanity.io/docs/studio/initial-value-templates) allow you to define default values for new documents. This plugin automatically generates these templates based on the `templates` property you define in your `ListItem`. ::: info Automatic Registration The `structure` utility handles template registration automatically for you. In most cases, you **do not** need to use this `templates` utility manually. ::: ::: warning Limitations with childOptions Automatic template generation does not support list items that define `schemaType`, `children`, or `templates` dynamically based on route-dependent `childOptions`. For these items, you must set `enableAutoGenerateTemplates: false` and register templates manually. ::: #### Advanced Use Case {#return-advanced-use-case} You only need this utility if you want to manually merge the plugin-generated templates with your own custom templates or templates from other plugins in your `sanity.config.ts`: ::: warning Prevent Duplicated Templates When manually merging templates as shown below, you **must** disable automatic template generation by setting `enableAutoGenerateTemplates: false` in your main plugin configuration. Otherwise, Sanity will register the generated templates twice, resulting in duplicates in the studio. ::: ::: code-group ```ts [sanity.config.ts] import { templates } from './src/structure'; import listItems from './src/structure/listItems'; export default defineConfig({ // ... schema: { templates: (prev, context) => { // 1. Get templates from this plugin const pluginTemplates = templates({ listItems })(prev, context); // 2. Add your own custom templates return [ ...pluginTemplates, { id: 'custom-template', title: 'Custom Template', schemaType: 'post', value: { title: 'New Post' }, }, ]; }, }, }); ``` ::: --- --- url: /guide/setup/define-list-items.md --- # Define List Items {#define-list-items} Once you have [configured the structure](./configuration), you can build your studio's desk hierarchy. The plugin provides both a set of type-safe `helpers` and standard `define` utilities for raw objects. You should keep your list items in a separate file (e.g., `src/structure/listItems.ts`). ## Defining Items {#defining-items} You can define your structure using either `helpers` for a cleaner syntax and better type intelligence, or raw objects using `defineListItems`. Both approaches are fully type-safe. For a detailed reference of all helper methods like `helpers.listing`, `helpers.singleton`, and `helpers.divider`, check the [Type-Safe Helpers Guide](../helpers). If you prefer not to import the generated `helpers` object in every structure file, `defineListItems` accepts a callback function that passes `{ helpers }` directly. ::: code-group ```ts [JSON] import { defineListItems } from './index'; const listItems = defineListItems([ { title: 'General', isDivider: true, }, { schemaType: 'author', }, { title: 'Settings', schemaType: 'settings', singleton: true, }, { title: 'System', isDivider: true, }, { title: 'Config', children: [ { schemaType: 'apiSettings', singleton: true, }, { schemaType: 'siteBranding', singleton: true, }, ], }, ]); export default listItems; ``` ```ts [Helpers (Import)] import { defineListItems, helpers } from './index'; const listItems = defineListItems([ helpers.divider('General'), helpers.listing('author'), helpers.singleton('settings'), helpers.divider('System'), helpers.children('Config', [helpers.singleton('apiSettings'), helpers.singleton('siteBranding')]), ]); export default listItems; ``` ```ts [Helpers (Callback)] import { defineListItems } from './index'; const listItems = defineListItems(({ helpers }) => [ helpers.divider('General'), helpers.listing('author'), helpers.singleton('settings'), helpers.divider('System'), helpers.children('Config', [helpers.singleton('apiSettings'), helpers.singleton('siteBranding')]), ]); export default listItems; ``` ::: ## Why use `defineListItems`? {#why-use-define-list-items} While you could define your structure as a plain array, using `defineListItems` (or the generated `helpers`) offers several key advantages: 1. **Type Safety**: Ensures every item follows the `ListItem` schema. 2. **Contextual IntelliSense**: If you configured `roles` or `workspaces` in your [setup](./configuration), they will be available as autocomplete options within your list items. 3. **Validation**: Catches common mistakes, like missing a `title` when an item has `children`. You can also use the generated `helpers` directly for a cleaner syntax and better autocomplete. Learn more in the [Type-Safe Helpers Guide](../helpers). ## Individual Items {#individual-items} If you need to define and export a single item (for example, to reuse it across different lists), use the [`defineListItem`](./configuration#return-define-list-item) utility. Alternatively, you can use the generated `helpers` (like `helpers.listing` or `helpers.singleton`) directly for better type safety, autocomplete, and cleaner syntax. Just like `defineListItems`, the `defineListItem` utility also supports a callback function that passes the `helpers` context, so you don't have to import it locally: ::: code-group ```ts [JSON] import { defineListItem } from './index'; export const blogSection = defineListItem({ title: 'Blog', schemaType: 'post', }); ``` ```ts [Helpers (Import)] import { helpers } from './index'; export const blogSection = helpers.listing('post', { title: 'Blog', }); ``` ```ts [Helpers (Callback)] import { defineListItem } from './index'; export const blogSection = defineListItem(({ helpers }) => helpers.listing('post', { title: 'Blog', }), ); ``` ::: ## Key Item Properties {#key-item-properties} The `ListItem` configuration supports a wide range of properties. For a full list of available fields and their usage, see the **[List Items Guide](../list-items)**. ## Important Note on Workspaces & Roles {#important-note-on-workspaces-roles} The availability of `workspaces` and `roles` properties on your list items depends entirely on your initial [configuration](./configuration). * **Workspace Protection**: The `workspaces` property will only be available if they were explicitly configured at the plugin level. * **Role Protection**: Similarly, if `roles` were not defined during setup, the `roles` property will be hidden from types, preventing you from using the roles. This strict coupling ensures that your access control logic remains consistent and type-safe throughout your project. --- --- url: /guide/setup/i18n.md --- # Internationalization (i18n) Setup {#i18n-setup} To enable automatic internationalization for list items, you must configure custom translation resource bundles on the `structureToolPlugin` initialization. ## Configuration {#configuration} Pass your translation resource files into the `i18n` option dictionary, mapping each supported locale code (e.g., `'en-US'` or `'es-ES'`) to its corresponding resources: ```ts [src/structure/index.ts] import { structureToolPlugin } from 'sanity-plugin-structure-tool'; import en from './locales/en.json'; import es from './locales/es.json'; export const { structure, templates, defineListItems } = structureToolPlugin({ title: 'Project Name', // Register translation locale resource bundles i18n: { 'en-US': { resources: en, }, 'es-ES': { resources: es, }, }, }); ``` ## Registering Custom Languages {#custom-languages} English (`en-US`) translation support is enabled by default in Sanity Studio. If you want to support other languages (like Spanish `'es-ES'`), you need to install and register the corresponding official locale plugin. You can find all available locale plugins in the [Sanity Locales repository](https://github.com/sanity-io/locales). ::: info Example: Spanish Setup Install `@sanity/locale-es-es`, then register it in your `sanity.config.ts`: ```ts [sanity.config.ts] import { defineConfig } from 'sanity'; import { esESLocale } from '@sanity/locale-es-es'; // Import the locale plugin import { structure } from './src/structure'; export default defineConfig({ // ... other configurations plugins: [ structure({ listItems, }), esESLocale(), // Register the locale plugin ], }); ``` ::: ## Nested JSON Keys {#nested-json-keys} Translation keys inside list items support standard dot-notation (e.g. `drawer.folder`) to access nested translation values within the registered JSON structure. This enables you to organize translation keys hierarchically: ::: code-group ```ts [JSON] { title: 'drawer.folder', i18n: true, children: [ { title: 'drawer.level_1', i18n: true, schemaType: 'author', } ] } ``` ```ts [Helpers] helpers.children({ title: 'drawer.folder', i18n: true, children: [ helpers.listing('author', { title: 'drawer.level_1', i18n: true, }), ], }); ``` ```json [en.json] { "drawer": { "folder": "Drawer", "level_1": "Level 1 Depth" } } ``` ```json [es.json] { "drawer": { "folder": "CajΓ³n", "level_1": "Nivel de Profundidad 1" } } ``` ::: ## Usage Examples {#usage-examples} Once setup is complete, you can enable localization on individual list items by setting `i18n: true`. See the [i18n Examples](../../examples/i18n) page for complete usage configurations. ## Overwrites & Fallbacks {#overwrites-and-fallbacks} When `i18n: true` is set on a list item, the string configured as its `title` will be treated as the translation key rather than a static display title. The active locale's translation value will overwrite the original title. * **Locale Fallback**: If the translation key is missing in the active locale, Sanity Studio will attempt to fall back to the English (`en-US`) translation bundle. * **Missing Key Fallback**: If the key is not defined in any registered translation resource bundle, the title will fall back to displaying the raw key string itself (e.g., `'authors'`). --- --- url: /guide/list-items.md --- # List Items {#list-items} The core of **Sanity Structure Tool** is the `ListItem` configuration. This guide provides a complete index of all configuration properties. ::: info Using Helpers You can define list items using either raw objects or the built-in [Helpers](./helpers). Helpers provide enhanced type intelligence and a more expressive syntax. ::: ## Dynamic Values (Callbacks) {#dynamic-values} Almost every property on a `ListItem` supports **dynamic values**. Instead of passing a static value, you can pass a callback function that receives the active desk context: ```ts ({ workspace, currentUser, context }) => value; ``` This dynamic callback pattern allows you to compute structure values dynamically based on the current workspace, logged-in user, or Sanity context. ### Callback Parameters | Parameter | Type | Description | | :------------- | :--------------------- | :-------------------------------------------------------------------------------------------------- | | `workspace` | `string` | The active workspace name. | | `currentUser` | `CurrentUser` | The currently logged-in Sanity user. | | `context` | `ConfigContext` | The raw Sanity config context. | | `childOptions` | `ChildResolverOptions` | Optional. Resolver options from the active child pane structure resolution context (Sanity Studio). | ::: info Resolver-level Callback Parameters The `childOptions` parameter is passed to callback properties that resolve recursively down a structural path (e.g. `children`, `showIcons`, `filter`, `componentOptions`, `menuItems`, etc.). This enables access to current route parameters, parent structure references, or payload values. ::: ### Example ```ts helpers.listing('author', { // Compute title dynamically based on workspace title: ({ workspace }) => (workspace === 'staging' ? 'Review Authors' : 'Authors'), // Hide add button dynamically for non-admin users hideAddButton: ({ currentUser }) => !currentUser.roles.some((role) => role.name === 'administrator'), }); ``` ## Property Reference {#properties} Click on any property name below to view its complete type definition, details, and interactive usage examples (Standard JSON vs Helpers). | Property | Optional | Description | | :-------------------------------------------------- | :-------------------- | :--------------------------------------------------------------------- | | [`apiVersion`](../examples/api-version) | Yes | Specifies the Sanity API version to use for this specific list item. | | [`children`](../examples/children) | Yes | An array of `ListItem` objects to create a nested list. | | [`component`](../examples/component) | Yes | Renders a custom React component as the view for a list item. | | [`componentOptions`](../examples/component-options) | Yes | Passes custom options or properties to your custom component. | | [`defaultLayout`](../examples/default-layout) | Yes | Specifies the default layout style for documents listed. | | [`defaultOrdering`](../examples/default-ordering) | Yes | Sets the default sorting order for document lists. | | [`defaultPanes`](../examples/default-panes) | Yes | Defines which view pane tabs are active/open side-by-side by default. | | [`filter`](../examples/filter) | Yes | A GROQ filter string to limit which documents are shown. | | [`filterParams`](../examples/filter) | Yes | Parameters to be used within the `filter` GROQ string. | | [`hideAddButton`](../examples/hide-add-button) | Yes | Hides the "Add" button (plus icon) for the document list. | | [`i18n`](../examples/i18n) | Yes | Enables automatic translation of display titles using locale bundles. | | [`icon`](../examples/icon) | Yes | The icon to display to the left of the title. | | [`id`](../examples/id) | Yes | Uniquely identifies the list item in the desk menu path. | | [`isDivider`](../examples/is-divider) | Yes | Renders as a visual separator in the desk list. | | [`isPlural`](../examples/is-plural) | Yes | Controls automatic pluralization of the auto-generated title. | | [`isVisible`](../examples/is-visible) | Yes (Default: `true`) | Controls the visibility of the list item in the navigation menu. | | [`menuItemGroups`](../examples/menu-item-groups) | Yes | Groups custom actions/items in the pane header menu. | | [`menuItems`](../examples/menu-items) | Yes | Defines custom actions/items in the pane header menu. | | [`raw`](../examples/raw) | Yes | The "Escape Hatch" to use the native Sanity Structure Builder API. | | [`roles`](../examples/roles) | Yes | Restricts the visibility of the list item to specific user roles. | | [`schemaType`](../examples/schema-type) | Yes | The name of the document type defined in your Sanity schema. | | [`showIcons`](../examples/show-icons) | Yes | Controls whether icons are displayed for items inside this list. | | [`singleton`](../examples/singleton) | Yes | Treats the item as a single document rather than a list. | | [`templates`](../examples/templates) | Yes | Passes initial value templates for new documents. | | [`title`](../examples/title) | Yes | The display name for the list item in the Sanity desk menu. | | [`views`](../examples/views) | Yes | Defines multiple pane tabs (views) for singletons or document editors. | | [`workspaces`](../examples/workspaces) | Yes | Restricts the visibility of the list item to specific workspaces. | --- --- url: /guide/helpers.md --- # Helpers {#helpers} The **Sanity Structure Tool** provides a set of helper functions that are **strictly typed** to your project's specific configuration. These helpers are generated by a factory function (`structureToolPlugin`), ensuring that your custom `roles` and `workspaces` are fully available via IntelliSense and validated at compile-time. ## The Factory Pattern {#the-factory-pattern} When you initialize the plugin, you receive a `helpers` object that is pre-bound to your types, which you then use to define your structure list items: ::: code-group ```ts [src/structure/index.ts] import { structureToolPlugin } from 'sanity-plugin-structure-tool'; export const { helpers } = structureToolPlugin({ // 1. Define your project-specific roles and workspaces roles: ['administrator', 'editor', 'viewer'], workspaces: ['production', 'staging'], }); ``` ```ts [src/structure/listItems.ts] helpers.listing('post', { // Roles and Workspaces are now strictly typed! roles: ['administrator'], workspaces: ['production'], }); ``` ::: ## `helpers.children` {#children} * **Shorthand Signature**: `(title: string, children: ListItem[], params?: CoreParams) => ChildrenOutput` * **Object Signature**: `(params: ChildrenParams) => ChildrenOutput` * **Examples**: [See Examples](../examples/children) Creates a nested list structure (folder). ::: code-group ```ts [JSON] { title: 'Settings', children: [ { schemaType: 'generalSettings', singleton: true, }, { schemaType: 'apiSettings', singleton: true, }, ], } ``` ```ts [Helpers (Short)] helpers.children('Settings', [ helpers.singleton('generalSettings'), helpers.singleton('apiSettings'), ]); ``` ```ts [Helpers (Object)] helpers.children({ title: 'Settings', children: [helpers.singleton('generalSettings'), helpers.singleton('apiSettings')], }); ``` ::: ## `helpers.component` {#component} * **Shorthand Signature**: `(title: string, component: ComponentType, params?: CoreParams) => ComponentOutput` * **Object Signature**: `(params: ComponentParams) => ComponentOutput` * **Examples**: [See Examples](../examples/component) Renders a custom React component as the pane content. ::: code-group ```ts [JSON] { title: 'Dashboard', component: MyDashboard, } ``` ```ts [Helpers (Short)] helpers.component('Dashboard', MyDashboard); ``` ```ts [Helpers (Object)] helpers.component({ title: 'Dashboard', component: MyDashboard, }); ``` ::: ## `helpers.divider` {#divider} * **Shorthand Signature**: `(title?: string, params?: CoreParams) => DividerOutput` * **Object Signature**: `(params?: DividerParams) => DividerOutput` * **Examples**: [See Examples](../examples/is-divider) Renders a visual separator in the desk list. ::: code-group ```ts [JSON] { title: 'Content Section', isDivider: true, } ``` ```ts [Helpers (Short)] helpers.divider('Content Section'); ``` ```ts [Helpers (Object)] helpers.divider({ title: 'Content Section', }); ``` ::: ## `helpers.filters` {#filters} * **Type**: `(params: FiltersParams) => FiltersOutput` * **Examples**: [See Examples](../examples/filter) Creates a filtered list (e.g., "Drafts", "Published"). ::: code-group ```ts [JSON] { title: 'Published Posts', filter: '_type == "post" && !(_id in path("drafts.**"))', } ``` ```ts [Helpers] helpers.filters({ title: 'Published Posts', filter: '_type == "post" && !(_id in path("drafts.**"))', }); ``` ::: ## `helpers.listing` {#listing} * **Shorthand Signature**: `(schemaType: string, params?: CoreParams) => ListingOutput` * **Object Signature**: `(params: ListingParams) => ListingOutput` * **Examples**: [See Examples](../examples/schema-type) Used to define a standard document list. ::: code-group ```ts [JSON] { title: 'All Authors', schemaType: 'author', icon: UserIcon, } ``` ```ts [Helpers (Short)] helpers.listing('author', { title: 'All Authors', }); ``` ```ts [Helpers (Object)] helpers.listing({ title: 'All Authors', schemaType: 'author', icon: UserIcon, }); ``` ::: ## `helpers.raw` {#raw} * **Shorthand Signature**: `(raw: (S: StructureBuilder, context: any) => any, params?: CoreParams) => RawOutput` * **Object Signature**: `(params: RawParams) => RawOutput` * **Examples**: [See Examples](../examples/raw) The "Escape Hatch" to use the native Sanity Structure Builder API. ::: code-group ```ts [JSON] { raw: (S) => S.listItem().title('Advanced').child(...), } ``` ```ts [Helpers (Short)] helpers.raw((S) => S.listItem().title('Advanced').child(...)) ``` ```ts [Helpers (Object)] helpers.raw({ raw: (S) => S.listItem().title('Advanced').child(...) }) ``` ::: ## `helpers.singleton` {#singleton} * **Shorthand Signature**: `(schemaType: string, params?: CoreParams) => SingletonOutput` * **Object Signature**: `(params: SingletonParams) => SingletonOutput` * **Examples**: [See Examples](../examples/singleton) Used for singleton documents (documents that only have one instance). ::: code-group ```ts [JSON] { title: 'Global Settings', schemaType: 'settings', singleton: true, } ``` ```ts [Helpers (Short)] helpers.singleton('settings', { title: 'Global Settings', }); ``` ```ts [Helpers (Object)] helpers.singleton({ title: 'Global Settings', schemaType: 'settings', }); ``` ::: ## Why a Factory Pattern? {#why-factory} By using a factory function (`structureToolPlugin`) to generate your helpers, we ensure that: 1. **Context is preserved**: Helpers are pre-bound and know the specific "shape" of your structure. 2. **No generic "string" types**: Roles and Workspaces are treated as literal unions, not just broad strings. 3. **Refactoring is easy**: If you change a role name in your configuration, TypeScript will immediately highlight all the helpers that need to be updated. ## Usage via Callbacks (No Imports) {#usage-via-callbacks} If you do not want to import the generated `helpers` object in every structure file, both `defineListItems` and `defineListItem` support a callback function that passes the bound `helpers` as an argument. This is especially useful for modular structures: ::: code-group ```ts [src/structure/listItems.ts] import { defineListItems } from './index'; export default defineListItems(({ helpers }) => [ helpers.divider('General'), helpers.listing('author'), helpers.singleton('settings'), ]); ``` ::: ## Advantages of Helpers {#advantages-of-helpers} Using the built-in helpers offers several key advantages: 1. **Readability**: The syntax is often more expressive and closer to natural language. 2. **Type Safety**: Every helper is strictly typed to ensure you provide the correct parameters for each item type. 3. **Type Intelligence**: If you configured `roles` or `workspaces` in your [setup](./setup/configuration), they will be available as autocomplete options within the helper parameters. 4. **Shorthand Syntax**: Many helpers allow you to pass the `schemaType` or `title` as the first argument, reducing boilerplate. --- --- url: /guide/comparison.md --- # Comparison: Plugin vs Native {#comparison-plugin-vs-native} **Sanity Structure Tool** is designed to eliminate the repetitive boilerplate required by the native Sanity Structure Builder. This guide demonstrates the code reduction and clarity gained by switching to a declarative approach. ## Standard Desk Hierarchy {#standard-desk} In a typical studio, you often need to handle singletons (like "Settings"), dividers, and standard document lists. ::: code-group ```ts [JSON] const listItems = defineListItems([ { title: 'Site Settings', schemaType: 'settings', // Automatically handles id and editor view singleton: true, }, { isDivider: true, }, { // Automatically pluralizes title and adds icon schemaType: 'post', }, ]); ``` ```ts [Helpers] const listItems = defineListItems([ // Automatically handles id and editor view helpers.singleton('settings', { title: 'Site Settings', }), helpers.divider(), // Automatically pluralizes title and adds icon helpers.listing('post'), ]); ``` ```ts [Native Structure Builder] export const structure = (S) => S.list() .title('Desk') .items([ // 1. Singletons require manual id and child definition S.listItem() .title('Site Settings') .id('settings') .child(S.document().schemaType('settings').documentId('settings')), S.divider(), // 2. Standard items require explicit list item calls S.documentTypeListItem('post').title('Posts'), ]); ``` ::: ## Access Control (Workspaces & Roles) {#access-control-workspaces-roles} Managing visibility based on user roles or workspaces is where the native API becomes extremely repetitive. ::: code-group ```ts [JSON] // Automatically handles logic for both roles and workspaces const listItems = defineListItems([ { schemaType: 'revenue', workspaces: ['staging'], roles: ['administrator'], }, ]); ``` ```ts [Helpers] const listItems = defineListItems([ helpers.listing('revenue', { workspaces: ['staging'], roles: ['administrator'], }), ]); ``` ```ts [Native Structure Builder] export const structure = (S, context) => { const { currentUser, dataset } = context; const isAdmin = currentUser.roles.some((r) => r.name === 'administrator'); const items = []; // Manual filtering logic for every protected item if (isAdmin && dataset === 'staging') { items.push(S.documentTypeListItem('revenue')); } return S.list().title('Desk').items(items); }; ``` ::: ## Key Advantages {#key-advantages} | Feature | Sanity Structure Tool | Native Builder | | :------------------- | :----------------------------------- | :------------------------------- | | **Syntax** | **Declarative (JSON like)** | Imperative (Method Chaining) | | **Singletons** | **One property (`singleton: true`)** | Manual setup (id, view, actions) | | **Type Safety** | **Contextual & Typed** | Generic | | **Boilerplate** | **Minimal** | High (repeats for every item) | | **Roles/Workspaces** | **Built-in protection** | Manual `if/else` logic | ## Summary {#summary} By using the **Sanity Structure Tool**, you trade complex method chaining for a clean, typed configuration. This not only reduces the amount of code you write but also makes your structure easier to read, maintain, and protect. --- --- url: /guide/faq.md --- # Frequently Asked Questions {#faq} Here are some of the most common questions and clarifications about using `sanity-plugin-structure-tool`. This section will help you understand differences, setup tips, and design decisions behind the package. ## 1. What is Sanity Structure Tool? {#what-is-sanity-structure-tool} It is a declarative, JSON-based wrapper around Sanity's `structureTool`. It allows you to define your studio's desk structure using a configuration object instead of the imperative `Structure Builder` API. ## 2. Can I use it alongside the standard Structure Builder? {#can-i-use-it-alongside-standard-structure-builder} Yes! If you have a highly complex requirement that the JSON API doesn't support yet, you can use the `raw` property or `helpers.raw` to drop back into the native `Structure Builder` (S). ::: code-group ```ts [JSON] { raw: (S) => S.listItem().title('Custom').child(...) } ``` ```ts [Helpers] helpers.raw((S) => S.listItem().title('Custom').child(...)) ``` ::: ## 3. Does it support Singletons? {#does-it-support-singletons} Absolutely. Singletons are a first-class citizen. You can use `helpers.singleton('yourType')` or add `singleton: true` to your JSON config, and the tool will handle the document ID, editor view, and list filtering for you. ## 4. How do I handle user roles? {#how-do-i-handle-user-roles} The tool has built-in support for roles. You can define `roles` at the plugin level and then restrict specific list items using the `roles` property on any helper or JSON object. ## 5. Is it TypeScript compatible? {#is-it-typescript-compatible} Yes, it is built with TypeScript and provides full type safety and autocompletion for your structure definitions, especially when using the generated helpers. ## 6. Why should I use this instead of the native API? {#why-should-i-use-this-instead-of-native-api} While the native API is powerful, it can become very verbose and hard to maintain as your studio grows. This tool provides a cleaner, more readable hierarchy that is easier to refactor and manage, especially for common patterns like singletons and nested lists. For a detailed side-by-side example, check out our **[Comparison Guide](./comparison)**. ## 7. Why do JSON configurations and Helpers (Object) properties match? {#why-do-json-and-helpers-match} You will notice that the properties in a plain JSON configuration and a `Helpers (Object)` call match exactly. The helper function wrapper is provided so that the TypeScript compiler can actively identify and validate the precise parameters you can use (e.g., checking if roles/workspaces are enabled in your setup configuration), offering inline IntelliSense and error checking while keeping the object structure you are familiar with. --- --- url: /examples/usage.md --- # Usage {#usage} There are several ways to explore the features of **Sanity Structure Tool**. Whether you prefer reading code, seeing a live demo, or running the studio locally, we have you covered. ## 1. Documentation (Recommended) {#documentation-recommended} The fastest way to understand a specific property is to browse the individual example pages in the sidebar. Each page focuses on a single field and provides clear, copy-pasteable snippets. * **[title](./title)** * **[schemaType](./schema-type)** * **[icon](./icon)** * **[singleton](./singleton)** * **[children](./children)** * **...and more in the sidebar!** ## 2. Interactive Live Demo {#interactive-live-demo} If you want to see the **Sanity Structure Tool** in action without setting anything up, visit our live demo studio: πŸ‘‰ **[Live Demo Studio](https://sanity-structure-tool-studio.nishargshah.dev/)** ::: info Note You will need to log in with your own Sanity account and **send an access request** to the owner. Please note that it can take up to **24 hours** to resolve the request. Since the basic Sanity plan has a limit of 20 users, access is managed manually to ensure everyone has a chance to explore the demo. ::: ## 3. Local Development {#local-development} For developers who want to experiment with the code and see it run on their own machine, you can clone the repository and run the example studio locally. ### Steps: {#steps} 1. **Repository Setup**: Follow our **[Contributing Guide](../contribute/guide)** to clone the repo and install dependencies. 2. **Environment Setup**: Create a `.env` file in the `apps/studio` folder with your own `projectId` and `dataset`. 3. **Import Sample Data**: We provide a production data snapshot in the `data` folder. You can import it using the [Sanity CLI](https://www.sanity.io/docs/content-lake/importing-data): ```sh sanity dataset import data/production.tar.gz ``` 4. **Run the Studio**: ```sh pnpm studio:dev ``` This setup gives you a fully functional playground to test complex structures, roles, and workspaces. --- --- url: /examples/api-version.md --- # `apiVersion` {#api-version} * **Type**: `string | ((params: CallbackParams & { childOptions: ChildResolverOptions }) => string)` * **Optional**: Yes The `apiVersion` property allows you to specify the Sanity API version for a specific list item. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] { schemaType: 'author', apiVersion: '2025-02-19', } ``` ```ts [Helpers] helpers.listing('author', { apiVersion: '2025-02-19', }); ``` ::: ## Dynamic API Version (Callback) {#dynamic-api-version} You can specify the `apiVersion` dynamically using a callback function: ::: code-group ```ts [JSON] { schemaType: 'author', apiVersion: ({ workspace }) => workspace === 'production' ? '2026-06-19' : '2025-02-19', } ``` ```ts [Helpers] helpers.listing('author', { apiVersion: ({ workspace }) => (workspace === 'production' ? '2026-06-19' : '2025-02-19'), }); ``` ::: --- --- url: /examples/children.md --- # `children` {#children} * **Type**: `ListItem[] | ((params: CallbackParams) => ListItem[])` * **Optional**: Yes The `children` property allows you to create nested list structures. You can nest children multiple levels deep to create complex hierarchies. ## Standard Nesting {#standard-nesting} ::: code-group ```ts [JSON] { title: 'Profile', children: [ { schemaType: 'author', }, { schemaType: 'user', }, ], } ``` ```ts [Helpers] helpers.children('Profile', [helpers.listing('author'), helpers.listing('user')]); ``` ::: ## Deep Nesting (Nested Children) {#deep-nesting} ::: code-group ```ts [JSON] { title: 'Content Management', children: [ { title: 'Marketing', children: [ { title: 'Campaigns', children: [ { schemaType: 'summerSale', }, { schemaType: 'winterSale', }, ], }, { schemaType: 'adChannel', }, ], }, { schemaType: 'blogPost', }, ], } ``` ```ts [Helpers] helpers.children('Content Management', [ helpers.children('Marketing', [ helpers.children('Campaigns', [helpers.listing('summerSale'), helpers.listing('winterSale')]), helpers.listing('adChannel'), ]), helpers.listing('blogPost'), ]); ``` ::: ## Dynamic Children (Callback) {#dynamic-children} You can define `children` dynamically using a callback function: ::: code-group ```ts [JSON] { title: 'Content', children: ({ workspace }) => workspace === 'blog' ? [{ schemaType: 'post' }] : [{ schemaType: 'product' }], } ``` ```ts [Helpers] helpers.children('Content', ({ workspace }) => workspace === 'blog' ? [helpers.listing('post')] : [helpers.listing('product')], ); ``` ::: ## With Child Resolver Options {#with-child-resolver-options} You can access the `childOptions` passed by Sanity Studio's structure resolver to inspect current route parameters, parent pane references, or custom payload: ::: code-group ```ts [JSON] { title: 'Dynamic Comments', children: ({ childOptions }) => { const parentId = childOptions.parent?.id; return [ { schemaType: 'comment', filter: `post._ref == $parentId`, filterParams: { parentId }, } ]; } } ``` ```ts [Helpers] helpers.children('Dynamic Comments', ({ childOptions }) => { const parentId = childOptions.parent?.id; return [ helpers.listing('comment', { filter: `post._ref == $parentId`, filterParams: { parentId }, }), ]; }); ``` ::: --- --- url: /examples/component.md --- # `component` {#component} * **Type**: `UserComponent` * **Optional**: Yes The `component` property allows you to render a custom React component as the child (view) of the list item. This is useful for creating custom dashboards, analytics views, or any other non-document based content. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] import { MyDashboard } from './components/MyDashboard'; { title: 'Analytics Dashboard', component: MyDashboard, } ``` ```ts [Helpers] import { MyDashboard } from './components/MyDashboard'; helpers.component('Analytics Dashboard', MyDashboard); ``` ::: --- --- url: /examples/component-options.md --- # `componentOptions` {#component-options} * **Type**: `Record | ((params: CallbackParams & { childOptions: ChildResolverOptions }) => Record)` * **Optional**: Yes The `componentOptions` property allows you to pass custom configuration options or parameters to your custom React component rendered via the `component` property. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] import { MyDashboard } from './components/MyDashboard'; { title: 'Analytics Dashboard', component: MyDashboard, componentOptions: { refreshInterval: 5000, theme: 'dark', }, } ``` ```ts [Helpers] import { MyDashboard } from './components/MyDashboard'; helpers.component('Analytics Dashboard', MyDashboard, { componentOptions: { refreshInterval: 5000, theme: 'dark', }, }); ``` ::: ## Dynamic Options (Callback) {#dynamic-options} You can dynamically resolve `componentOptions` using a callback function based on the active desk context. ::: code-group ```ts [JSON] import { MyDashboard } from './components/MyDashboard'; { title: 'Analytics Dashboard', component: MyDashboard, componentOptions: ({ workspace, currentUser }) => ({ workspaceName: workspace, userEmail: currentUser.email, }), } ``` ```ts [Helpers] import { MyDashboard } from './components/MyDashboard'; helpers.component('Analytics Dashboard', MyDashboard, { componentOptions: ({ workspace, currentUser }) => ({ workspaceName: workspace, userEmail: currentUser.email, }), }); ``` ::: --- --- url: /examples/default-layout.md --- # `defaultLayout` {#default-layout} * **Type**: `'default' | 'card' | 'media' | 'detail' | 'block' | ((params: CallbackParams & { childOptions: ChildResolverOptions }) => 'default' | 'card' | 'media' | 'detail' | 'block')` * **Optional**: Yes The `defaultLayout` property specifies the default layout style for documents listed in the desk menu. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] { title: 'Media Gallery', schemaType: 'mediaItem', defaultLayout: 'media', } ``` ```ts [Helpers] helpers.listing('mediaItem', { title: 'Media Gallery', defaultLayout: 'media', }); ``` ::: ## Dynamic Layout (Callback) {#dynamic-layout} You can dynamically switch layout style using a callback function based on the active desk context. ::: code-group ```ts [JSON] { title: 'Portfolio Items', schemaType: 'portfolio', defaultLayout: ({ workspace }) => (workspace === 'creative' ? 'media' : 'default'), } ``` ```ts [Helpers] helpers.listing('portfolio', { title: 'Portfolio Items', defaultLayout: ({ workspace }) => (workspace === 'creative' ? 'media' : 'default'), }); ``` ::: --- --- url: /examples/default-ordering.md --- # `defaultOrdering` {#default-ordering} * **Type**: `Record> | ((params: CallbackParams & { childOptions: ChildResolverOptions }) => Record>)` * **Optional**: Yes The `defaultOrdering` property sets the default sorting order for document lists. You can specify one or more fields to sort by, in ascending or descending order. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] { title: 'Recent Articles', schemaType: 'article', defaultOrdering: { publishedAt: 'desc', title: 'asc', }, } ``` ```ts [Helpers] helpers.listing('article', { title: 'Recent Articles', defaultOrdering: { publishedAt: 'desc', title: 'asc', }, }); ``` ::: ## Dynamic Ordering (Callback) {#dynamic-ordering} You can dynamically set the sorting order using a callback function based on the active desk context. ::: code-group ```ts [JSON] { title: 'Articles', schemaType: 'article', defaultOrdering: ({ workspace }) => ({ // Sort differently based on the workspace _createdAt: workspace === 'production' ? 'desc' : 'asc', }), } ``` ```ts [Helpers] helpers.listing('article', { title: 'Articles', defaultOrdering: ({ workspace }) => ({ // Sort differently based on the workspace _createdAt: workspace === 'production' ? 'desc' : 'asc', }), }); ``` ::: --- --- url: /examples/default-panes.md --- # `defaultPanes` {#default-panes} * **Type**: `string[] | ((params: CallbackParams & { childOptions: ChildResolverOptions; views: string[] }) => string[])` * **Optional**: Yes The `defaultPanes` property allows you to define which pane tabs (views) are active/open side-by-side by default when a custom document editor or singleton is resolved. ## Standard Usage {#standard-usage} By default, Sanity resolves the first tab (usually `editor`/`form`). You can use `defaultPanes` to specify different default views or display multiple views side-by-side: ::: code-group ```ts [JSON] import { CogIcon, BookIcon } from '@sanity/icons'; import { DocumentationComponent } from './components/Documentation'; { schemaType: 'settings', singleton: true, views: [ { title: 'Setting Form', type: 'form', id: 'setting-form', icon: CogIcon, }, { title: 'Documentation', type: 'component', id: 'documentation', icon: BookIcon, component: DocumentationComponent, options: { url: 'https://example.com/docs', }, }, ], // Open both the editor form and documentation views side-by-side defaultPanes: ['setting-form', 'documentation'], } ``` ```ts [Helpers] import { CogIcon, BookIcon } from '@sanity/icons'; import { DocumentationComponent } from './components/Documentation'; helpers.singleton('settings', { views: [ { title: 'Setting Form', type: 'form', id: 'setting-form', icon: CogIcon, }, { title: 'Documentation', type: 'component', id: 'documentation', icon: BookIcon, component: DocumentationComponent, options: { url: 'https://example.com/docs', }, }, ], // Open both the editor form and documentation views side-by-side defaultPanes: ['setting-form', 'documentation'], }); ``` ::: ## Dynamic Default Panes (Callback) {#dynamic-default-panes} You can dynamically set default panes using a callback function based on the active desk context. The callback receives standard parameters plus a `views` array containing the IDs of all configured views: ::: code-group ```ts [JSON] import { CogIcon, BookIcon } from '@sanity/icons'; import { DocumentationComponent } from './components/Documentation'; { schemaType: 'settings', singleton: true, views: [ { title: 'Setting Form', type: 'form', id: 'setting-form', icon: CogIcon, }, { title: 'Documentation', type: 'component', id: 'documentation', icon: BookIcon, component: DocumentationComponent, options: { url: 'https://example.com/docs', }, }, ], // Dynamically open all views side-by-side or just the form defaultPanes: ({ views, workspace }) => workspace === 'staging' ? views : ['setting-form'], } ``` ```ts [Helpers] import { CogIcon, BookIcon } from '@sanity/icons'; import { DocumentationComponent } from './components/Documentation'; helpers.singleton('settings', { views: [ { title: 'Setting Form', type: 'form', id: 'setting-form', icon: CogIcon, }, { title: 'Documentation', type: 'component', id: 'documentation', icon: BookIcon, component: DocumentationComponent, options: { url: 'https://example.com/docs', }, }, ], // Dynamically open all views side-by-side or just the form defaultPanes: ({ views, workspace }) => (workspace === 'staging' ? views : ['setting-form']), }); ``` ::: --- --- url: /examples/filter.md --- # `filter` and `filterParams` {#filter-filter-params} The `filter` and `filterParams` properties allow you to customize and limit the documents shown in a list item using GROQ queries. Together, they enable you to build scoped, conditional, and role-based views of your datasets. ## `filter` * **Type**: `string | ((params: CallbackParams & { childOptions: ChildResolverOptions }) => string)` * **Optional**: Yes A GROQ filter string to limit which documents are shown in the list. You can also pass a function that returns a filter string based on the current user. ## `filterParams` * **Type**: `Record | ((params: CallbackParams & { childOptions: ChildResolverOptions }) => Record)` * **Optional**: Yes Parameters to be used within the `filter` GROQ string. ## Basic Filtering {#basic-filtering} You can use `filter` alongside `schemaType` to show a subset of documents. ::: code-group ```ts [JSON] { title: 'Active Authors', schemaType: 'author', filter: 'isActive == true', } ``` ```ts [Helpers] helpers.listing('author', { title: 'Active Authors', filter: 'isActive == true', }); ``` ::: ## Organized Sub-sections {#organized-sub-sections} Filters are commonly used within `children` to create organized views of the same document type. ::: code-group ```ts [JSON] { title: 'Authors', children: [ { title: 'Active', schemaType: 'author', filter: 'isActive == true', }, { title: 'Inactive', schemaType: 'author', filter: 'isActive != true', hideAddButton: true, }, ], } ``` ```ts [Helpers] helpers.children('Authors', [ helpers.listing('author', { title: 'Active', filter: 'isActive == true', }), helpers.listing('author', { title: 'Inactive', filter: 'isActive != true', hideAddButton: true, }), ]); ``` ::: ## Using Filter Parameters {#using-filter-parameters} Use `filterParams` to pass dynamic values to your GROQ query. ::: code-group ```ts [JSON] { title: 'Authors from GROQ', filter: '_type == $author', filterParams: { author: 'author', }, } ``` ```ts [Helpers] helpers.filters({ title: 'Authors from GROQ', filter: '_type == $author', filterParams: { author: 'author', }, }); ``` ::: ## Multiple Document Types {#multiple-document-types} You can create a mixed list of multiple document types by using a more complex GROQ filter. ::: code-group ```ts [JSON] { title: 'Authors + Homepage from GROQ', filter: '_type == $author || _type == $homepage', filterParams: { author: 'author', homepage: 'homepage', }, } ``` ```ts [Helpers] helpers.filters({ title: 'Authors + Homepage from GROQ', filter: '_type == $author || _type == $homepage', filterParams: { author: 'author', homepage: 'homepage', }, }); ``` ::: ## Function-based Filtering {#function-based-filtering} You can pass a function to both `filter` and `filterParams` to dynamically control the list based on the current user. The following two examples achieve the exact same result: ### 1. Using Dynamic Filter String {#using-dynamic-filter-string} In this approach, you return the entire GROQ string from the `filter` function. ::: code-group ```ts [JSON] { title: 'My Posts', schemaType: 'post', filter: ({ currentUser }) => `author == "${currentUser.id}"`, } ``` ```ts [Helpers] helpers.listing('post', { title: 'My Posts', filter: ({ currentUser }) => `author == "${currentUser.id}"`, }); ``` ::: ### 2. Using Dynamic Filter Parameters {#using-dynamic-filter-parameters} In this approach, you keep the `filter` string static and use a function for `filterParams` to pass the user ID. ::: code-group ```ts [JSON] { title: 'My Posts', schemaType: 'post', filter: 'author == $userId', filterParams: ({ currentUser }) => ({ userId: currentUser.id, }), } ``` ```ts [Helpers] helpers.listing('post', { title: 'My Posts', filter: 'author == $userId', filterParams: ({ currentUser }) => ({ userId: currentUser.id, }), }); ``` ::: ### 3. Combining Both {#combining-both} You can also combine both for more complex logic. ::: code-group ```ts [JSON] { title: 'My Role-based Documents', schemaType: 'post', filter: ({ currentUser }) => currentUser.roles.includes('administrator') ? 'status == $status' : 'author == $userId && status == $status', filterParams: ({ currentUser }) => ({ status: 'published', userId: currentUser.id, }), } ``` ```ts [Helpers] helpers.listing('post', { title: 'My Role-based Documents', filter: ({ currentUser }) => currentUser.roles.includes('administrator') ? 'status == $status' : 'author == $userId && status == $status', filterParams: ({ currentUser }) => ({ status: 'published', userId: currentUser.id, }), }); ``` ::: ## Combined with Roles & Workspaces {#combined-with-roles-workspaces} Filters work seamlessly with other properties like `roles` and `workspaces`. ::: code-group ```ts [JSON] { title: 'Internal Authors', schemaType: 'author', workspaces: ['admin-workspace'], roles: ['administrator'], filter: 'isInternal == true', } ``` ```ts [Helpers] helpers.listing('author', { title: 'Internal Authors', workspaces: ['admin-workspace'], roles: ['administrator'], filter: 'isInternal == true', }); ``` ::: ## Dynamic Filter based on Workspace (Callback) {#dynamic-filter-workspace} You can define the `filter` and `filterParams` dynamically using workspace context: ::: code-group ```ts [JSON] { schemaType: 'author', filter: ({ workspace }) => workspace === 'production' ? 'status == "active"' : 'true', } ``` ```ts [Helpers] helpers.listing('author', { filter: ({ workspace }) => (workspace === 'production' ? 'status == "active"' : 'true'), }); ``` ::: --- --- url: /examples/hide-add-button.md --- # `hideAddButton` {#hide-add-button} * **Type**: `boolean | ((params: CallbackParams & { childOptions: ChildResolverOptions }) => boolean)` * **Optional**: Yes The `hideAddButton` property removes the "Add" (plus) icon from the document list. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] { schemaType: 'author', hideAddButton: true, } ``` ```ts [Helpers] helpers.listing('author', { hideAddButton: true, }); ``` ::: ## Dynamic Hide Add Button (Callback) {#dynamic-hide-add-button} You can define `hideAddButton` dynamically using a callback function: ::: code-group ```ts [JSON] { schemaType: 'author', hideAddButton: ({ currentUser }) => !currentUser.roles.some((role) => role.name === 'administrator'), } ``` ```ts [Helpers] helpers.listing('author', { hideAddButton: ({ currentUser }) => !currentUser.roles.some((role) => role.name === 'administrator'), }); ``` ::: --- --- url: /examples/i18n.md --- # `i18n` {#i18n} * **Type**: `boolean | ((params: CallbackParams) => boolean)` * **Optional**: Yes The `i18n` property enables automatic internationalization for a list item's display titles (both parent and child pane titles) using translation resource bundles registered with Sanity Studio's i18n framework. When enabled, translation keys are resolved dynamically from translation namespaces. You can also specify different translation keys for the parent and child titles. ## Standard Usage {#standard-usage} Register translation resource bundles in your plugin setup, then configure list items to use translations: ::: code-group ```ts [JSON] { title: 'authors', schemaType: 'author', i18n: true, } ``` ```ts [Helpers] helpers.listing('author', { title: 'authors', i18n: true, }); ``` ::: ## Parent & Child Titles {#parent-child-titles} You can translate parent and child titles separately by passing translation keys inside a `title` object: ::: code-group ```ts [JSON] { schemaType: 'author', title: { parent: 'parent_title', child: 'child_title', }, i18n: true, } ``` ```ts [Helpers] helpers.listing('author', { title: { parent: 'parent_title', child: 'child_title', }, i18n: true, }); ``` ::: --- --- url: /examples/icon.md --- # `icon` {#icon} * **Type**: `IconComponent | ComponentType | ReactNode | false` * **Optional**: Yes The `icon` property allows you to add a visual indicator to the left of the title. You can use standard Sanity icons, custom React components, or pass `false` to explicitly disable/hide the icon. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] import { UsersIcon } from '@sanity/icons'; { schemaType: 'author', icon: UsersIcon, } ``` ```ts [Helpers] import { UsersIcon } from '@sanity/icons'; helpers.listing('author', { icon: UsersIcon, }); ``` ::: ## Disabling Icon {#disabling-icon} Set `icon: false` to explicitly hide the icon for the list item in the desk menu. ::: code-group ```ts [JSON] { schemaType: 'author', icon: false, } ``` ```ts [Helpers] helpers.listing('author', { icon: false, }); ``` ::: --- --- url: /examples/id.md --- # `id` {#id} * **Type**: `string | ((params: CallbackParams & { values: ListItemIdValues }) => string)` * **Optional**: Yes The `id` property uniquely identifies the list item in the Sanity desk menu path. If not provided, it is automatically generated using the unique item index and the slugified title. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] { id: 'custom-author-id', title: 'Authors', schemaType: 'author', } ``` ```ts [Helpers] helpers.listing('author', { id: 'custom-author-id', title: 'Authors', }); ``` ::: ## Dynamic ID (Callback) {#dynamic-id} You can dynamically construct or customize the `id` using a callback. The callback receives standard desk context (`workspace`, `currentUser`, `context`) plus a `values` object containing auto-generated default values: ### Callback `values` Object | Property | Type | Description | | :--------------- | :------------------------ | :-------------------------------------------------------------------------------------- | | `uniqueId` | `string` | The base index-based ID generated by the plugin (e.g. `1.2.3`). | | `sanitizedPaths` | `string[]` | An array of words from the title, sanitized and ready for URL use. | | `id` | `string` | The default ID string generated by the plugin (combining uniqueId and sanitized paths). | | `slugify` | `(str: string) => string` | The internal sanitize/slugify utility function. | ### Example ::: code-group ```ts [JSON] import { constants } from 'sanity-plugin-structure-tool'; { title: 'My Custom Category', schemaType: 'category', // Dynamically prefix or suffix the ID based on active workspace id: ({ workspace, values }) => { return [workspace, values.uniqueId, 'category'].join(constants.URL_PATH_SEPARATOR); }, } ``` ```ts [Helpers] import { constants } from 'sanity-plugin-structure-tool'; helpers.listing('category', { title: 'My Custom Category', id: ({ workspace, values }) => { return [workspace, values.uniqueId, 'category'].join(constants.URL_PATH_SEPARATOR); }, }); ``` ::: For more details on the path separator constant, see the **[URL\_PATH\_SEPARATOR](../customization/constants#url-path-separator)** documentation. --- --- url: /examples/is-divider.md --- # `isDivider` {#is-divider} * **Type**: `boolean | ((params: CallbackParams) => boolean)` * **Optional**: Yes The `isDivider` property renders a visual separator in the desk list. ## Simple Divider {#simple-divider} ::: code-group ```ts [JSON] { isDivider: true, } ``` ```ts [Helpers] helpers.divider(); ``` ::: ## Divider with Title {#divider-with-title} ::: code-group ```ts [JSON] { title: 'Settings', isDivider: true, } ``` ```ts [Helpers] helpers.divider('Settings'); ``` ::: --- --- url: /examples/is-plural.md --- # `isPlural` {#is-plural} * **Type**: `boolean | ((params: CallbackParams) => boolean)` * **Optional**: Yes The `isPlural` property controls whether the auto-generated title should be pluralized when no custom `title` is provided. When it is set to `false`, the plugin will showcase the exact same name you have defined in your schema, without any pluralization logic applied. ::: info Note For items marked as `singleton: true`, pluralization is **disabled by default** since singletons are singular by nature. However, you can manually set `isPlural: true` if you wish to pluralize a singleton's title. ::: ::: info Recommendation It is best to give your `schema` a **singular** title (e.g., `Author` instead of `Authors`). The plugin will then automatically pluralize it for the list view (e.g., "Authors"). ::: ## Standard Usage {#standard-usage} By default, the plugin pluralizes the schema name (e.g., "Author" becomes "Authors"). Set `isPlural: false` to disable this. ::: code-group ```ts [JSON] { schemaType: 'author', isPlural: false, } ``` ```ts [Helpers] helpers.listing('author', { isPlural: false, }); ``` ::: ## With Singletons {#with-singletons} Singletons have `isPlural: false` by default. You can manually enable it if you want the singleton's title to be pluralized. ::: code-group ```ts [JSON] { schemaType: 'settings', singleton: true, isPlural: true, } ``` ```ts [Helpers] helpers.singleton('settings', { isPlural: true, }); ``` ::: ## Dynamic Pluralization (Callback) {#dynamic-pluralization} You can set `isPlural` dynamically using a callback function: ::: code-group ```ts [JSON] { schemaType: 'author', isPlural: ({ workspace }) => workspace === 'production', } ``` ```ts [Helpers] helpers.listing('author', { isPlural: ({ workspace }) => workspace === 'production', }); ``` ::: --- --- url: /examples/is-visible.md --- # `isVisible` {#is-visible} * **Type**: `boolean | ((params: CallbackParams) => boolean)` * **Optional**: Yes (Default: `true`) The `isVisible` property controls whether the list item is visible in the Sanity desk navigation menu. ## Standard Usage {#standard-usage} Set `isVisible: false` to hide a list item from the navigation pane completely. ::: code-group ```ts [JSON] { schemaType: 'author', isVisible: false, } ``` ```ts [Helpers] helpers.listing('author', { isVisible: false, }); ``` ::: ## Dynamic Visibility (Callback) {#dynamic-visibility} You can set `isVisible` dynamically using a callback function based on the active desk context. This is useful for showing/hiding items depending on the active workspace or current user roles. ::: code-group ```ts [JSON] { schemaType: 'settings', singleton: true, isVisible: ({ currentUser }) => currentUser.roles.some((role) => role.name === 'administrator'), } ``` ```ts [Helpers] helpers.singleton('settings', { isVisible: ({ currentUser }) => currentUser.roles.some((role) => role.name === 'administrator'), }); ``` ::: --- --- url: /examples/menu-item-groups.md --- # `menuItemGroups` {#menu-item-groups} * **Type**: `MenuItemGroup[] | ((params: CallbackParams & { prev: MenuItemGroup[]; childOptions: ChildResolverOptions }) => MenuItemGroup[])` * **Optional**: Yes The `menuItemGroups` property allows you to group multiple custom menu items under collapsible sections or specific categories in the pane header menu. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] import { DownloadIcon, TrashIcon } from '@sanity/icons'; { title: 'Articles', schemaType: 'article', menuItemGroups: [ { id: 'actions-group', title: 'Database Actions', }, ], menuItems: [ { title: 'Export to CSV', icon: DownloadIcon, action: 'export-csv', group: 'actions-group', }, { title: 'Purge Records', icon: TrashIcon, action: 'purge', group: 'actions-group', }, ], } ``` ```ts [Helpers] import { DownloadIcon, TrashIcon } from '@sanity/icons'; helpers.listing('article', { title: 'Articles', menuItemGroups: [ { id: 'actions-group', title: 'Database Actions', }, ], menuItems: [ { title: 'Export to CSV', icon: DownloadIcon, action: 'export-csv', group: 'actions-group', }, { title: 'Purge Records', icon: TrashIcon, action: 'purge', group: 'actions-group', }, ], }); ``` ::: ## Working with Existing Groups (`prev`) {#working-with-prev} When using a callback function, the callback parameters object includes a `prev` property containing the default menu item groups from Sanity Studio. You can use this to append, prepend, or filter existing groups. ::: code-group ```ts [JSON] { title: 'Articles', schemaType: 'article', menuItemGroups: ({ prev }) => [ ...prev, { id: 'custom-group', title: 'Custom Actions', }, ], } ``` ```ts [Helpers] helpers.listing('article', { title: 'Articles', menuItemGroups: ({ prev }) => [ ...prev, { id: 'custom-group', title: 'Custom Actions', }, ], }); ``` ::: ## Dynamic Menu Groups (Callback) {#dynamic-menu-groups} You can dynamically configure the menu groups using a callback function based on the active desk context. ::: code-group ```ts [JSON] { title: 'Articles', schemaType: 'article', menuItemGroups: ({ workspace }) => [ { id: 'actions-group', title: `${workspace === 'production' ? 'Live' : 'Staging'} Actions`, }, ], } ``` ```ts [Helpers] helpers.listing('article', { title: 'Articles', menuItemGroups: ({ workspace }) => [ { id: 'actions-group', title: `${workspace === 'production' ? 'Live' : 'Staging'} Actions`, }, ], }); ``` ::: For defining menu items, see the **[menuItems](./menu-items)** page. --- --- url: /examples/menu-items.md --- # `menuItems` {#menu-items} * **Type**: `MenuItem[] | ((params: CallbackParams & { prev: MenuItem[]; childOptions: ChildResolverOptions }) => MenuItem[])` * **Optional**: Yes The `menuItems` property allows you to define custom actions in the pane header menu of document lists, custom components, or standard lists. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] import { DownloadIcon } from '@sanity/icons'; { title: 'Articles', schemaType: 'article', menuItems: [ { title: 'Export to CSV', icon: DownloadIcon, action: 'export-csv', showAsAction: true, }, ], } ``` ```ts [Helpers] import { DownloadIcon } from '@sanity/icons'; helpers.listing('article', { title: 'Articles', menuItems: [ { title: 'Export to CSV', icon: DownloadIcon, action: 'export-csv', showAsAction: true, }, ], }); ``` ::: ## Working with Existing Menu Items (`prev`) {#working-with-prev} When using a callback function, the callback parameters object includes a `prev` property containing the default menu items generated by Sanity Studio. This is useful for appending, prepending, or filtering default actions. ::: code-group ```ts [JSON] import { DownloadIcon } from '@sanity/icons'; { title: 'Articles', schemaType: 'article', menuItems: ({ prev }) => [ ...prev, { title: 'Export to CSV', icon: DownloadIcon, action: 'export-csv', }, ], } ``` ```ts [Helpers] import { DownloadIcon } from '@sanity/icons'; helpers.listing('article', { title: 'Articles', menuItems: ({ prev }) => [ ...prev, { title: 'Export to CSV', icon: DownloadIcon, action: 'export-csv', }, ], }); ``` ::: ## Dynamic Menu Items (Callback) {#dynamic-menu-items} You can dynamically show or hide menu items using a callback function based on the active desk context. ::: code-group ```ts [JSON] import { DownloadIcon, TrashIcon } from '@sanity/icons'; { title: 'Articles', schemaType: 'article', menuItems: ({ currentUser }) => { const items = [ { title: 'Export', icon: DownloadIcon, action: 'export', showAsAction: true, }, ]; if (currentUser.roles.some((role) => role.name === 'administrator')) { items.push({ title: 'Purge Deleted', icon: TrashIcon, action: 'purge', showAsAction: false, }); } return items; }, } ``` ```ts [Helpers] import { DownloadIcon, TrashIcon } from '@sanity/icons'; helpers.listing('article', { title: 'Articles', menuItems: ({ currentUser }) => { const items = [ { title: 'Export', icon: DownloadIcon, action: 'export', showAsAction: true, }, ]; if (currentUser.roles.some((role) => role.name === 'administrator')) { items.push({ title: 'Purge Deleted', icon: TrashIcon, action: 'purge', showAsAction: false, }); } return items; }, }); ``` ::: For grouping menu items, see the **[menuItemGroups](./menu-item-groups)** page. --- --- url: /examples/raw.md --- # `raw` {#raw} * **Type**: `(S: StructureBuilder, context: ConfigContext) => ListItem` * **Optional**: Yes The `raw` property serves as the "Escape Hatch". It allows you to bypass the declarative JSON configuration and use the native [Sanity Structure Builder API](https://www.sanity.io/docs/structure-builder-introduction) directly for a specific list item. ## When to use `raw`? {#when-to-use-raw} You should only use `raw` when you need functionality that isn't currently supported by the plugin's declarative schema, such as: * Customizing the document list sorting or grouping. * Using complex `child` resolvers. * Creating completely custom views or components in the desk. ## Constraints & Limitations {#constraints-limitations} While the `raw` property is powerful, it comes with specific constraints: * **Return Type**: The `raw` callback **must** return an `S.listItem()` (or its equivalent piped output). It does not support `S.list()` or other builders as the root return value. * **Access Control**: When you use `raw`, the plugin's automatic **`roles`** and **`workspaces`** filtering only applies to the top-level list item itself. If you define nested `children` within the `raw` callback using the native API, you are responsible for handling any access control logic manually for those nested items. ## Basic Usage {#basic-usage} In this example, we use the native `S.listItem()` to create a highly customized entry. ::: code-group ```ts [JSON] { raw: (S) => S.listItem() .title('Custom Sorted Posts') .child( S.documentTypeList('post') .title('Posts by Title') .filter('_type == "post"') .defaultOrdering([{ field: 'title', direction: 'asc' }]) ), } ``` ```ts [Helpers] helpers.raw((S) => S.listItem() .title('Custom Sorted Posts') .child( S.documentTypeList('post') .title('Posts by Title') .filter('_type == "post"') .defaultOrdering([{ field: 'title', direction: 'asc' }]), ), ); ``` ::: ## Accessing Context {#accessing-context} The `raw` callback also provides access to the Sanity `context`, which includes the `currentUser`, `dataset`, `projectId`, and more. ::: code-group ```ts [JSON] { raw: (S, context) => { const { currentUser } = context; return S.listItem() .title(`My Assigned Tasks (${currentUser?.name})`) .child( S.documentTypeList('task') .filter('_type == "task" && assignee._ref == $userId') .params({ userId: currentUser?.id }) ); }, } ``` ```ts [Helpers] helpers.raw((S, context) => { const { currentUser } = context; return S.listItem() .title(`My Assigned Tasks (${currentUser?.name})`) .child( S.documentTypeList('task') .filter('_type == "task" && assignee._ref == $userId') .params({ userId: currentUser?.id }), ); }); ``` ::: ::: warning Use Sparingly While powerful, using `raw` breaks away from the declarative benefits of this plugin. We recommend using it only when the standard properties (`schemaType`, `filter`, `singleton`, etc.) are insufficient for your needs. ::: --- --- url: /examples/roles.md --- # `roles` {#roles} * **Type**: `string[] | ((params: CallbackParams & { defaultRoles: string[] }) => string[])` * **Optional**: Yes The `roles` property allows you to restrict the visibility of the list item to specific user roles. Like `workspaces`, this can be a static array or a function receiving the active desk context (`workspace`, `currentUser`, `context`) and the `defaultRoles` defined in your configuration. ::: info Note When using a **static array**, the provided values are **concatenated** with the `defaultRoles`. When using a **callback function**, the returned array is treated as the **final value**, giving you full control over the resulting list. ::: ::: info Prerequisite To use this property, you must first define your available roles in the [plugin configuration](../guide/setup/configuration#roles). ::: ## Static Roles {#static-roles} When you provide a static array, the roles you list are **concatenated** with the `defaultRoles` defined in your configuration. ::: code-group ```ts [JSON] { title: 'Global Settings', schemaType: 'settings', singleton: true, // This item will appear for 'administrator' and all default roles roles: ['administrator'], } ``` ```ts [Helpers] helpers.singleton('settings', { title: 'Global Settings', // This item will appear for 'administrator' and all default roles roles: ['administrator'], }); ``` ::: ## Dynamic Roles (Callback) {#dynamic-roles-callback} Using a callback function gives you full control. Unlike the static array, the returned value of a callback is treated as the **final list**, meaning it does not automatically merge with defaults. ### 1. Exclusive Visibility {#exclusive-visibility} Use a callback to return a static array if you want the item to appear **only** for specific roles, ignoring the `defaultRoles`. ::: code-group ```ts [JSON] { title: 'Financial Reports', schemaType: 'revenue', // By using a callback, we ensure this ONLY appears for 'finance-admin' // even if other roles are set as defaults. roles: () => ['finance-admin'], } ``` ```ts [Helpers] helpers.listing('revenue', { title: 'Financial Reports', // By using a callback, we ensure this ONLY appears for 'finance-admin' // even if other roles are set as defaults. roles: () => ['finance-admin'], }); ``` ::: ### 2. Filtering Defaults {#filtering-defaults} You can dynamically filter the `defaultRoles` based on your project's logic. ::: code-group ```ts [JSON] { title: 'Editor Dashboard', schemaType: 'dashboard', // Dynamically show for all default roles except 'viewer' roles: ({ defaultRoles }) => { return defaultRoles.filter((role) => role !== 'viewer'); }, } ``` ```ts [Helpers] helpers.listing('dashboard', { title: 'Editor Dashboard', // Dynamically show for all default roles except 'viewer' roles: ({ defaultRoles }) => { return defaultRoles.filter((role) => role !== 'viewer'); }, }); ``` ::: ### 3. Role Restriction based on Workspace {#role-restriction-based-on-workspace} You can use the active `workspace` parameter to dynamically customize role access. ::: code-group ```ts [JSON] { title: 'Feedback', schemaType: 'feedback', // In the internal testing workspace, allow 'tester' role in addition to default roles roles: ({ defaultRoles, workspace }) => { if (workspace === 'internal-testing') { return [...defaultRoles, 'tester']; } return defaultRoles; }, } ``` ```ts [Helpers] helpers.listing('feedback', { title: 'Feedback', // In the internal testing workspace, allow 'tester' role in addition to default roles roles: ({ defaultRoles, workspace }) => { if (workspace === 'internal-testing') { return [...defaultRoles, 'tester']; } return defaultRoles; }, }); ``` ::: ### 4. Using with Workspaces {#using-with-workspaces} You can combine `roles` with the `workspaces` property to create multi-layered access control. This ensures an item is only visible for certain roles **and** only in specific workspaces. ::: code-group ```ts [JSON] { title: 'Internal Debug Tools', schemaType: 'debugInfo', // Visible only for the 'developer' role roles: () => ['developer'], // Only in the 'development-workspace' workspaces: () => ['development-workspace'], } ``` ```ts [Helpers] helpers.listing('debugInfo', { title: 'Internal Debug Tools', // Visible only for the 'developer' role roles: () => ['developer'], // Only in the 'development-workspace' workspaces: () => ['development-workspace'], }); ``` ::: For more details on workspace-based restrictions, see the **[workspaces](./workspaces)**. --- --- url: /examples/schema-type.md --- # `schemaType` {#schema-type} * **Type**: `string | ((params: CallbackParams) => string)` * **Optional**: Yes The `schemaType` property links the list item to a specific document type defined in your Sanity schema. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] { schemaType: 'author', } ``` ```ts [Helpers] helpers.listing('author'); ``` ::: ## With Custom Title {#with-custom-title} By default, the plugin generates a title based on the `schemaType`. You can override it using the `title` property. ::: code-group ```ts [JSON] { title: 'Contributors', schemaType: 'author', } ``` ```ts [Helpers] helpers.listing('author', { title: 'Contributors', }); ``` ::: ## With Custom Icon {#with-custom-icon} ::: code-group ```ts [JSON] import { UsersIcon } from '@sanity/icons'; { schemaType: 'author', icon: UsersIcon, } ``` ```ts [Helpers] import { UsersIcon } from '@sanity/icons'; helpers.listing('author', { icon: UsersIcon, }); ``` ::: ## With Combined (Title + Icon) {#with-combined} ::: code-group ```ts [JSON] import { UsersIcon } from '@sanity/icons'; { title: 'Contributors', schemaType: 'author', icon: UsersIcon, } ``` ```ts [Helpers] import { UsersIcon } from '@sanity/icons'; helpers.listing('author', { title: 'Contributors', icon: UsersIcon, }); ``` ::: ## With Disabled Pluralization {#with-disabled-pluralization} Use `isPlural: false` to display the singular name as defined in your schema. ::: code-group ```ts [JSON] { schemaType: 'author', isPlural: false, } ``` ```ts [Helpers] helpers.listing('author', { isPlural: false, }); ``` ::: ## Dynamic Schema Type (Callback) {#dynamic-schema-type} You can set the `schemaType` dynamically using a callback function: ::: code-group ```ts [JSON] { schemaType: ({ workspace }) => workspace === 'blog' ? 'post' : 'product', } ``` ```ts [Helpers] helpers.listing(({ workspace }) => (workspace === 'blog' ? 'post' : 'product')); ``` ::: --- --- url: /examples/show-icons.md --- # `showIcons` {#show-icons} * **Type**: `boolean | ((params: CallbackParams & { childOptions: ChildResolverOptions }) => boolean)` * **Optional**: Yes The `showIcons` property determines whether icons are displayed for items inside a list or document list. This corresponds to the `.showIcons(showIcons)` builder method in the Sanity Structure Builder API. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] { title: 'All Content', showIcons: false, children: [ { schemaType: 'post', }, { schemaType: 'author', }, ], } ``` ```ts [Helpers] helpers.children('All Content', [helpers.listing('post'), helpers.listing('author')], { showIcons: false, }); ``` ::: ## Dynamic Icons Display (Callback) {#dynamic-icons-display} You can dynamically show or hide icons based on the active desk context. ::: code-group ```ts [JSON] { title: 'Articles', schemaType: 'article', // Only show icons on staging workspace for visual checks showIcons: ({ workspace }) => workspace === 'staging', } ``` ```ts [Helpers] helpers.listing('article', { title: 'Articles', showIcons: ({ workspace }) => workspace === 'staging', }); ``` ::: --- --- url: /examples/singleton.md --- # `singleton` {#singleton} * **Type**: `boolean | ((params: CallbackParams) => boolean)` * **Optional**: Yes The `singleton` property treats the item as a single document rather than a list. The plugin will automatically handle the document ID and editor view. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] { schemaType: 'settings', singleton: true, } ``` ```ts [Helpers] helpers.singleton('settings'); ``` ::: ## Dynamic Singleton (Callback) {#dynamic-singleton} You can define the `singleton` property dynamically using a callback function: ::: code-group ```ts [JSON] { schemaType: 'settings', singleton: ({ workspace }) => workspace === 'production', } ``` ::: --- --- url: /examples/templates.md --- # `templates` {#templates} * **Type**: `Record | ((params: CallbackParams & { childOptions: ChildResolverOptions }) => Record)` * **Optional**: Yes The `templates` property allows you to define default values for new documents created from a specific list item. These are automatically mapped to [Sanity Initial Value Templates](https://www.sanity.io/docs/studio/initial-value-templates). ## Basic Usage {#basic-usage} In this example, when a user creates a new "Post" from this list item, the `status` field will default to `draft` and `publishedAt` will be set to the current date. ::: code-group ```ts [JSON] { schemaType: 'post', templates: { status: 'draft', publishedAt: new Date().toISOString(), }, } ``` ```ts [Helpers] helpers.listing('post', { templates: { status: 'draft', publishedAt: new Date().toISOString(), }, }); ``` ::: ## Multiple Templates for Same Type {#multiple-templates-for-same-type} You can define multiple list items for the same `schemaType` with different initial values. The plugin will automatically generate unique template IDs for each. ::: code-group ```ts [JSON] [ { title: 'Featured Posts', schemaType: 'post', templates: { isFeatured: true, category: 'featured', }, }, { title: 'News Posts', schemaType: 'post', templates: { category: 'news', }, }, ]; ``` ```ts [Helpers] [ helpers.listing('post', { title: 'Featured Posts', templates: { isFeatured: true, category: 'featured', }, }), helpers.listing('post', { title: 'News Posts', templates: { category: 'news', }, }), ]; ``` ::: ## Dynamic Templates (Callback) {#dynamic-templates} You can define initial value templates dynamically using a callback function: ::: code-group ```ts [JSON] { schemaType: 'post', templates: ({ workspace }) => ({ workspaceSource: workspace, isActive: workspace === 'production', }), } ``` ```ts [Helpers] helpers.listing('post', { templates: ({ workspace }) => ({ workspaceSource: workspace, isActive: workspace === 'production', }), }); ``` ::: ## Registration {#registration} The `structure` utility handles template registration automatically, keeping your desk items and initial values in sync. For advanced use cases and manual merging, see the **[Configuration Guide](../guide/setup/configuration#return-templates)**. --- --- url: /examples/title.md --- # `title` {#title} * **Type**: `string | TitleObject | ((params: CallbackParams) => string | TitleObject)` * **Optional**: Yes (Required if `children` is present) The `title` property sets the display name for the list item in the Sanity desk menu. ## Standard Usage {#standard-usage} ::: code-group ```ts [JSON] { title: 'My Custom Title', schemaType: 'author', } ``` ```ts [Helpers] helpers.listing('author', { title: 'My Custom Title', }); ``` ::: ## Parent & Child Titles {#parent-child-titles} You can specify a different title for when an item is listed in the parent list versus when it is opened as a child pane. This is done by passing a `TitleObject` containing `parent` and/or `child` keys. ::: code-group ```ts [JSON] { schemaType: 'author', title: { parent: 'Contributors', child: 'Authors', }, } ``` ```ts [Helpers] helpers.listing('author', { title: { parent: 'Contributors', child: 'Authors', }, }); ``` ::: ## With Children {#with-children} When `children` are present, `title` becomes **mandatory** to label the parent item in the desk menu. ::: code-group ```ts [JSON] { title: 'Profile', children: [ { schemaType: 'author', }, { schemaType: 'user', }, ], } ``` ```ts [Helpers] helpers.children('Profile', [helpers.listing('author'), helpers.listing('user')]); ``` ::: ## With Dividers {#with-dividers} You can use the `title` property with `helpers.divider` to create a labeled separator. ::: code-group ```ts [JSON] { title: 'Content Section', isDivider: true, } ``` ```ts [Helpers] helpers.divider('Content Section'); ``` ::: ## Dynamic Title (Callback) {#dynamic-title} You can set the `title` dynamically using a callback function: ::: code-group ```ts [JSON] { title: ({ workspace }) => `${workspace === 'production' ? 'Live' : 'Staging'} Settings`, schemaType: 'settings', singleton: true, } ``` ```ts [Helpers] helpers.singleton('settings', { title: ({ workspace }) => `${workspace === 'production' ? 'Live' : 'Staging'} Settings`, }); ``` ::: --- --- url: /examples/views.md --- # `views` {#views} * **Type**: `View[] | ((params: CallbackParams & { childOptions: ChildResolverOptions }) => View[])` * **Optional**: Yes The `views` property allows you to define multiple pane tabs (views) for singletons or custom document editors in Sanity Studio. Users can switch between these views using tabs in the pane header. ## Standard Usage {#standard-usage} In this example, we configure a settings singleton pane with two tabs: the default editing form and a custom React component showing documentation. ::: code-group ```ts [JSON] import { CogIcon, BookIcon } from '@sanity/icons'; import { DocumentationComponent } from './components/Documentation'; { schemaType: 'settings', singleton: true, views: [ { title: 'Setting Form', type: 'form', id: 'setting-form', icon: CogIcon, }, { title: 'Documentation', type: 'component', id: 'documentation', icon: BookIcon, component: DocumentationComponent, options: { url: 'https://example.com/docs', }, }, ], } ``` ```ts [Helpers] import { CogIcon, BookIcon } from '@sanity/icons'; import { DocumentationComponent } from './components/Documentation'; helpers.singleton('settings', { views: [ { title: 'Setting Form', type: 'form', id: 'setting-form', icon: CogIcon, }, { title: 'Documentation', type: 'component', id: 'documentation', icon: BookIcon, component: DocumentationComponent, options: { url: 'https://example.com/docs', }, }, ], }); ``` ::: ## Dynamic Views (Callback) {#dynamic-views} You can dynamically adjust the pane views using a callback function based on the active desk context: ::: code-group ```ts [JSON] import { CogIcon, BookIcon } from '@sanity/icons'; import { DocumentationComponent } from './components/Documentation'; { schemaType: 'settings', singleton: true, views: ({ workspace }) => [ { title: 'Setting Form', type: 'form', id: 'setting-form', icon: CogIcon, }, // Only show Documentation view on the staging workspace ...(workspace === 'staging' ? [{ title: 'Documentation', type: 'component', id: 'documentation', icon: BookIcon, component: DocumentationComponent, options: { url: 'https://example.com/docs', }, }] : []), ], } ``` ```ts [Helpers] import { CogIcon, BookIcon } from '@sanity/icons'; import { DocumentationComponent } from './components/Documentation'; helpers.singleton('settings', { views: ({ workspace }) => [ { title: 'Setting Form', type: 'form', id: 'setting-form', icon: CogIcon, }, // Only show Documentation view on the staging workspace ...(workspace === 'staging' ? [ { title: 'Documentation', type: 'component', id: 'documentation', icon: BookIcon, component: DocumentationComponent, options: { url: 'https://example.com/docs', }, }, ] : []), ], }); ``` ::: --- --- url: /examples/workspaces.md --- # `workspaces` {#workspaces} * **Type**: `string[] | ((params: CallbackParams & { defaultWorkspaces: string[] }) => string[])` * **Optional**: Yes The `workspaces` property allows you to restrict the visibility of the list item to specific Sanity workspaces. You can provide either a static array of workspaces or a function that returns an array based on the workspace, currentUser, context, and the `defaultWorkspaces` defined in your plugin configuration. ::: info Note When using a **static array**, the provided values are **concatenated** with the `defaultWorkspaces`. When using a **callback function**, the returned array is treated as the **final value**, giving you full control over the resulting list. ::: ::: info Prerequisite To use this property, you must first define your available workspaces in the [plugin configuration](../guide/setup/configuration#advanced-example). ::: ## Static Workspaces {#static-workspaces} When you provide a static array, the workspaces you list are **concatenated** with the `defaultWorkspaces` defined in your configuration. ::: code-group ```ts [JSON] { title: 'Admin Only Settings', schemaType: 'settings', singleton: true, // This item will appear in 'admin-workspace' and all default workspaces workspaces: ['admin-workspace'], } ``` ```ts [Helpers] helpers.singleton('settings', { title: 'Admin Only Settings', // This item will appear in 'admin-workspace' and all default workspaces workspaces: ['admin-workspace'], }); ``` ::: ## Dynamic Workspaces (Callback) {#dynamic-workspaces} Using a callback function gives you full control. Unlike the static array, the returned value of a callback is treated as the **final list**, meaning it does not automatically merge with defaults. ### 1. Exclusive Visibility {#exclusive-visibility} Use a callback to return a static array if you want the item to appear **only** in specific workspaces, ignoring the `defaultWorkspaces`. ::: code-group ```ts [JSON] { title: 'Staging Tools', schemaType: 'stagingConfig', // By using a callback, we ensure this ONLY appears in 'staging-workspace' // even if other workspaces are set as defaults. workspaces: () => ['staging-workspace'], } ``` ```ts [Helpers] helpers.listing('stagingConfig', { title: 'Staging Tools', // By using a callback, we ensure this ONLY appears in 'staging-workspace' // even if other workspaces are set as defaults. workspaces: () => ['staging-workspace'], }); ``` ::: ### 2. Filtering Defaults {#filtering-defaults} You can dynamically filter the `defaultWorkspaces` based on naming conventions or environment logic. ::: code-group ```ts [JSON] { title: 'Logs', schemaType: 'logs', // Dynamically show in all default workspaces except 'staging-workspace' workspaces: ({ defaultWorkspaces }) => { return defaultWorkspaces.filter((item) => item !== 'staging-workspace'); }, } ``` ```ts [Helpers] helpers.listing('logs', { title: 'Logs', // Dynamically show in all default workspaces except 'staging-workspace' workspaces: ({ defaultWorkspaces }) => { return defaultWorkspaces.filter((item) => item !== 'staging-workspace'); }, }); ``` ::: ### 3. Workspace Access via User Roles {#workspace-access-via-user-roles} You can use the logged-in user's roles (`currentUser`) to dynamically grant access to additional workspaces. ::: code-group ```ts [JSON] { title: 'System Settings', schemaType: 'systemSettings', singleton: true, // Show to administrators in all default workspaces plus 'admin-workspace' workspaces: ({ defaultWorkspaces, currentUser }) => { if (currentUser.roles.some((role) => role.name === 'administrator')) { return [...defaultWorkspaces, 'admin-workspace']; } return defaultWorkspaces; }, } ``` ```ts [Helpers] helpers.singleton('systemSettings', { title: 'System Settings', // Show to administrators in all default workspaces plus 'admin-workspace' workspaces: ({ defaultWorkspaces, currentUser }) => { if (currentUser.roles.some((role) => role.name === 'administrator')) { return [...defaultWorkspaces, 'admin-workspace']; } return defaultWorkspaces; }, }); ``` ::: ### 4. Using with Roles {#using-with-roles} You can combine `workspaces` with the `roles` property to create multi-layered access control. This ensures an item is only visible in specific workspaces **and** only to users with certain roles. ::: code-group ```ts [JSON] { title: 'Financial Reports', schemaType: 'revenue', // Visible only in 'finance-workspace' workspaces: () => ['finance-workspace'], // Only for users with the 'administrator' role roles: ['administrator'], } ``` ```ts [Helpers] helpers.listing('revenue', { title: 'Financial Reports', // Visible only in 'finance-workspace' workspaces: () => ['finance-workspace'], // Only for users with the 'administrator' role roles: ['administrator'], }); ``` ::: For more details on role-based restrictions, see the **[roles](./roles)**. --- --- url: /customization/constants.md --- # Constants {#constants} The **Sanity Structure Tool** exports the following constants that you can use in your configuration or custom document actions. *** ### `I18N_NAMESPACE` {#i18n-namespace} The default namespace key registered with the Sanity Studio internationalization framework. ### Usage Example {#usage-example-i18n-namespace} ```ts import { constants } from 'sanity-plugin-structure-tool'; console.log(constants.I18N_NAMESPACE); ``` *** ### `SINGLETON_KEY` {#singleton-key} This key is used as a suffix for document IDs when `singleton: true` is set. The final ID is generated as `${schemaType}-${SINGLETON_KEY}`. ### Usage Example {#usage-example} ```ts import { constants } from 'sanity-plugin-structure-tool'; // Use SINGLETON_KEY to check for singleton documents if (documentId.endsWith(constants.SINGLETON_KEY)) { // ... } ``` *** ### `UNIQUE_ID_FIRST_VALUE` {#unique-id-first-value} The default starting value used for auto-generated list item unique IDs. ### Usage Example {#usage-example-unique-id-first-value} ```ts import { constants } from 'sanity-plugin-structure-tool'; console.log(constants.UNIQUE_ID_FIRST_VALUE); ``` *** ### `URL_PATH_SEPARATOR` {#url-path-separator} The character used as a separator when generating unique list item paths and IDs. ### Usage Example {#usage-example-url-path-separator} ```ts import { constants } from 'sanity-plugin-structure-tool'; helpers.listing('category', { title: 'My Custom Category', id: ({ workspace, values }) => { return [workspace, values.uniqueId, 'custom'].join(constants.URL_PATH_SEPARATOR); }, }); ``` --- --- url: /customization/singleton-action.md --- # Singleton Action {#singleton-action} When you define a [Singleton](/examples/singleton) in your structure, you typically want to prevent users from performing certain actions that could break the singleton pattern, such as deleting the document, duplicating it, or unpublishing it. The **Sanity Structure Tool** provides a built-in `SingletonAction` helper to handle this automatically. ## How it Works {#how-it-works} The `SingletonAction` is a Sanity `DocumentActionsResolver`. It checks the ID of the document being edited. If the ID ends with `constants.SINGLETON_KEY` (which is how the plugin generates IDs for items marked as `singleton: true`), it filters out the following actions: * **Delete** * **Duplicate** * **Unpublish** This ensures that once a singleton document is created, it remains as a single, permanent piece of content in your studio. ## Usage {#usage} To use it, import `SingletonAction` and add it to the `document.actions` property in your `sanity.config.ts`. ::: code-group ```ts [sanity.config.ts] import { defineConfig } from 'sanity'; import { SingletonAction } from 'sanity-plugin-structure-tool'; import { structure } from './src/structure'; import listItems from './src/structure/listItems'; export default defineConfig({ // ... other config plugins: [ structure({ listItems, }), ], document: { // Add SingletonAction to handle document actions for singletons actions: SingletonAction, }, }); ``` ::: ## Customizing Actions {#customizing-actions} If you have other custom document actions, you can still use `SingletonAction`. Since it's a standard resolver, you can compose it with your own logic. You can also use the exported `constants` for your own checks: ::: code-group ```ts [sanity.config.ts] import { constants, SingletonAction } from 'sanity-plugin-structure-tool'; export default defineConfig({ // ... document: { actions: (prev, context) => { // First, let SingletonAction handle the filtering for singletons const actions = SingletonAction(prev, context); // Example of using constants manually if (context.documentId?.endsWith(constants.SINGLETON_KEY)) { // do something specific for singletons } // Then, add your custom logic if needed return actions; }, }, }); ``` ::: ::: info Note The `SingletonAction` only affects documents whose IDs end with `constants.SINGLETON_KEY`. It will not interfere with the standard actions of your other document types. ::: --- --- url: /contribute/guide.md --- # Contributing {#contributing} Thank you for considering contributing to `sanity-plugin-structure-tool`. We welcome all contributions, whether it’s fixing a bug, improving documentation, or suggesting new rules. ## How to Contribute {#how-to-contribute} ### 1. Fork & Clone the Repository {#for-clone-repository} ::: code-group ```sh [SSH] $ git clone git@github.com:sanity-plugin/structure-tool.git $ cd structure-tool ``` ```sh [HTTPS] $ git clone https://github.com/sanity-plugin/structure-tool.git $ cd structure-tool ``` ::: ### 2. Install Dependencies {#install-dependencies} Check the `.nvmrc` file for the required Node.js version. For `pnpm` version, see the `packageManager` field in the root `package.json`. This project is a **monorepo** managed with **pnpm**. Install dependencies with: ```sh $ pnpm install ``` ### 3. Project Structure {#project-structure} The repo is organized as a monorepo with two main packages: * `packages/sanity-plugin-structure-tool` β†’ The sanity structure tool package * `docs/` β†’ Documentation site (built with VitePress) ### 4. Making Changes {#making-changes} * Always create a new branch: ```sh $ git checkout -b fix/your-change ``` * For rule changes β†’ update the config. * For docs β†’ check formatting and verify links. ### 5. Linting & Formatting {#linting-formatting} Run checks and fixes before committing: ::: code-group ```sh [Check] $ pnpm lint $ pnpm format ``` ```sh [Fix] $ pnpm lint:fix $ pnpm format:fix ``` ::: ### 6. Commit Guidelines {#commit-guidelines} We follow **Conventional Commits** for a clean commit history. Examples: * `feat: add strict config for TypeScript` * `fix: resolve path issues` * `docs: update installation steps` ### 7. Running Scripts {#running-scripts} Before pushing, ensure all scripts pass: ```sh $ pnpm script:lint ``` ### 8. Submitting a PR {#submitting-pr} * Push your branch and open a Pull Request against `canary`. * Clearly describe the problem, your solution, and reference any related issues/discussions. * Maintainers will review, suggest improvements if needed, and merge once approved. ## Code of Conduct {#code-of-conduct} This project follows a [**Code of Conduct**](https://github.com/sanity-plugin/structure-tool/blob/master/CODE_OF_CONDUCT.md). Please be respectful, collaborative, and inclusive. ## Suggestions & Issues {#suggestions-issues} * Found a bug? β†’ [Open an Issue](https://github.com/sanity-plugin/structure-tool/issues/new/choose) * Want a new feature or rule? β†’ Use the same link to create an issue, or start a discussion before opening a PR. --- --- url: /README.md --- # @structure-tool/docs Structure Tool Documentation