Skip to main content
Tools are top-level views in Sanity Studio, accessible through the main navigation. They provide dedicated interfaces for content management, debugging, and custom functionality.

What are tools?

Tools are React components with routing integration that appear in Studio’s navigation bar. Each tool has:
  • Name - URL segment (e.g., /structure, /vision)
  • Title - Display name in navigation
  • Icon - Visual identifier
  • Component - React component to render
  • Router - Optional routing configuration

Built-in tools

Sanity Studio comes with three main built-in tools:

Structure tool

The primary interface for content editing:
import {defineConfig} from 'sanity'
import {structureTool} from 'sanity/structure'

export default defineConfig({
  // ...
  plugins: [
    structureTool(),
  ],
})
Features:
  • Document lists and filtering
  • Document editing forms
  • Customizable structure
  • Intent handling for create/edit

Vision tool

GROQ query playground for testing queries:
import {visionTool} from '@sanity/vision'

export default defineConfig({
  // ...
  plugins: [
    visionTool({
      defaultApiVersion: '2024-01-01',
      defaultDataset: 'production',
    }),
  ],
})
Features:
  • GROQ syntax highlighting
  • Real-time query results
  • Dataset switching
  • Query parameter support

Presentation tool

Visual editing with preview integration:
import {presentationTool} from 'sanity/presentation'

export default defineConfig({
  // ...
  plugins: [
    presentationTool({
      previewUrl: 'http://localhost:3000',
      allowOrigins: ['http://localhost:3000'],
    }),
  ],
})
Features:
  • Live preview of content
  • Visual editing overlays
  • Location resolver
  • Cross-origin preview support

Creating custom tools

Create a custom tool using definePlugin:
import {definePlugin} from 'sanity'
import {RocketIcon} from '@sanity/icons'
import {MyToolComponent} from './MyToolComponent'

export const myTool = definePlugin({
  name: 'my-tool-plugin',
  
  tools: [
    {
      name: 'my-tool',
      title: 'My Tool',
      icon: RocketIcon,
      component: MyToolComponent,
    },
  ],
})

Tool component

The tool component receives props including the tool configuration:
import {type Tool} from 'sanity'

export function MyToolComponent({tool}: {tool: Tool}) {
  return (
    <div style={{padding: '1rem'}}>
      <h1>{tool.title}</h1>
      <p>This is my custom tool!</p>
    </div>
  )
}

Tool with routing

Add routing to your tool for deep linking:
import {definePlugin} from 'sanity'
import {route} from 'sanity/router'
import {MyToolComponent} from './MyToolComponent'

export const myTool = definePlugin({
  name: 'my-tool-plugin',
  
  tools: [
    {
      name: 'my-tool',
      title: 'My Tool',
      component: MyToolComponent,
      router: route.create('/', [
        route.create('/view/:id'),
        route.create('/settings'),
      ]),
    },
  ],
})

Using router state

import {useRouterState} from 'sanity/router'

export function MyToolComponent() {
  const routerState = useRouterState()
  const {id} = routerState as {id?: string}
  
  return (
    <div>
      {id ? (
        <ViewDetail id={id} />
      ) : (
        <ViewList />
      )}
    </div>
  )
}
import {useRouter} from 'sanity/router'

export function MyToolComponent() {
  const router = useRouter()
  
  const handleClick = (id: string) => {
    router.navigateUrl({path: `/my-tool/view/${id}`})
  }
  
  return (
    <button onClick={() => handleClick('abc123')}>
      View Details
    </button>
  )
}

Intent handling

Tools can handle intents like “edit” or “create”:
import {definePlugin} from 'sanity'

export const myTool = definePlugin({
  name: 'my-tool-plugin',
  
  tools: [
    {
      name: 'my-tool',
      title: 'My Tool',
      component: MyToolComponent,
      
      canHandleIntent: (intent, params) => {
        // Return true if this tool can handle the intent
        if (intent === 'edit' && params.type === 'myType') {
          return true
        }
        return false
      },
      
      getIntentState: (intent, params) => {
        // Map intent to tool's router state
        if (intent === 'edit') {
          return {id: params.id}
        }
        return null
      },
    },
  ],
})

Tool with options

Pass options to configure tool behavior:
import {definePlugin} from 'sanity'

interface MyToolOptions {
  title?: string
  showSettings?: boolean
}

export const myTool = definePlugin<MyToolOptions>((options = {}) => {
  const {title = 'My Tool', showSettings = true} = options
  
  return {
    name: 'my-tool-plugin',
    
    tools: [
      {
        name: 'my-tool',
        title,
        component: (props) => (
          <MyToolComponent showSettings={showSettings} {...props} />
        ),
      },
    ],
  }
})
Usage:
export default defineConfig({
  // ...
  plugins: [
    myTool({
      title: 'Analytics',
      showSettings: false,
    }),
  ],
})

Accessing Studio context

Tools can access Studio context for project information and utilities:
import {useClient, useProjectId, useDataset, useSchema} from 'sanity'

export function MyToolComponent() {
  const client = useClient({apiVersion: '2024-01-01'})
  const projectId = useProjectId()
  const dataset = useDataset()
  const schema = useSchema()
  
  const fetchData = async () => {
    const data = await client.fetch('*[_type == "post"]')
    console.log(data)
  }
  
  return (
    <div>
      <p>Project: {projectId}</p>
      <p>Dataset: {dataset}</p>
      <button onClick={fetchData}>Fetch Posts</button>
    </div>
  )
}

Styling tools

Use Sanity UI components for consistent styling:
import {Card, Container, Heading, Stack, Text} from '@sanity/ui'

export function MyToolComponent() {
  return (
    <Container width={4}>
      <Card padding={4}>
        <Stack space={4}>
          <Heading as="h1">My Tool</Heading>
          <Text>
            This tool uses Sanity UI components for consistent styling.
          </Text>
        </Stack>
      </Card>
    </Container>
  )
}

Real-world tool examples

Analytics dashboard

import {definePlugin} from 'sanity'
import {ChartUpwardIcon} from '@sanity/icons'
import {Card, Container, Heading, Stack} from '@sanity/ui'
import {useEffect, useState} from 'react'
import {useClient} from 'sanity'

function AnalyticsDashboard() {
  const client = useClient({apiVersion: '2024-01-01'})
  const [stats, setStats] = useState(null)
  
  useEffect(() => {
    async function fetchStats() {
      const result = await client.fetch(`{
        "posts": count(*[_type == "post"]),
        "authors": count(*[_type == "author"]),
        "published": count(*[_type == "post" && !(_id in path("drafts.**"))])
      }`)
      setStats(result)
    }
    fetchStats()
  }, [])
  
  if (!stats) return <div>Loading...</div>
  
  return (
    <Container width={4}>
      <Card padding={4}>
        <Stack space={4}>
          <Heading>Analytics Dashboard</Heading>
          <Card padding={3} tone="primary">
            <Text>Total Posts: {stats.posts}</Text>
          </Card>
          <Card padding={3} tone="positive">
            <Text>Published: {stats.published}</Text>
          </Card>
          <Card padding={3}>
            <Text>Authors: {stats.authors}</Text>
          </Card>
        </Stack>
      </Card>
    </Container>
  )
}

export const analyticsPlugin = definePlugin({
  name: 'analytics-plugin',
  tools: [
    {
      name: 'analytics',
      title: 'Analytics',
      icon: ChartUpwardIcon,
      component: AnalyticsDashboard,
    },
  ],
})

Deployment tool

import {definePlugin} from 'sanity'
import {RocketIcon} from '@sanity/icons'
import {Button, Card, Container, Heading, Stack, Text} from '@sanity/ui'
import {useState} from 'react'

function DeploymentTool() {
  const [deploying, setDeploying] = useState(false)
  const [status, setStatus] = useState('')
  
  const handleDeploy = async () => {
    setDeploying(true)
    setStatus('Deploying...')
    
    try {
      // Trigger deployment via API
      await fetch('/api/deploy', {method: 'POST'})
      setStatus('Deployed successfully!')
    } catch (error) {
      setStatus(`Error: ${error.message}`)
    } finally {
      setDeploying(false)
    }
  }
  
  return (
    <Container width={2}>
      <Card padding={4}>
        <Stack space={4}>
          <Heading>Deploy Website</Heading>
          <Text>
            Trigger a new deployment of your production website.
          </Text>
          <Button
            text="Deploy Now"
            tone="primary"
            onClick={handleDeploy}
            disabled={deploying}
          />
          {status && <Text>{status}</Text>}
        </Stack>
      </Card>
    </Container>
  )
}

export const deploymentPlugin = definePlugin({
  name: 'deployment-plugin',
  tools: [
    {
      name: 'deploy',
      title: 'Deploy',
      icon: RocketIcon,
      component: DeploymentTool,
    },
  ],
})

Content report tool

import {definePlugin} from 'sanity'
import {DocumentTextIcon} from '@sanity/icons'
import {Card, Container, Heading, Stack, Table} from '@sanity/ui'
import {useEffect, useState} from 'react'
import {useClient} from 'sanity'

function ContentReportTool() {
  const client = useClient({apiVersion: '2024-01-01'})
  const [report, setReport] = useState([])
  
  useEffect(() => {
    async function generateReport() {
      const posts = await client.fetch(`
        *[_type == "post"] {
          _id,
          title,
          "wordCount": length(pt::text(content)),
          "author": author->name,
          publishedAt
        } | order(publishedAt desc)
      `)
      setReport(posts)
    }
    generateReport()
  }, [])
  
  return (
    <Container width={5}>
      <Card padding={4}>
        <Stack space={4}>
          <Heading>Content Report</Heading>
          <Table>
            <thead>
              <tr>
                <th>Title</th>
                <th>Author</th>
                <th>Word Count</th>
                <th>Published</th>
              </tr>
            </thead>
            <tbody>
              {report.map((post) => (
                <tr key={post._id}>
                  <td>{post.title}</td>
                  <td>{post.author}</td>
                  <td>{post.wordCount}</td>
                  <td>{new Date(post.publishedAt).toLocaleDateString()}</td>
                </tr>
              ))}
            </tbody>
          </Table>
        </Stack>
      </Card>
    </Container>
  )
}

export const contentReportPlugin = definePlugin({
  name: 'content-report-plugin',
  tools: [
    {
      name: 'report',
      title: 'Content Report',
      icon: DocumentTextIcon,
      component: ContentReportTool,
    },
  ],
})

Filtering tools by user role

Restrict tool access based on user permissions:
export default defineConfig({
  // ...
  tools: (prev, {currentUser}) => {
    const isAdmin = currentUser?.roles.some(
      (role) => role.name === 'administrator'
    )
    
    // Remove sensitive tools for non-admins
    if (!isAdmin) {
      return prev.filter(
        (tool) => !['vision', 'deploy'].includes(tool.name)
      )
    }
    
    return prev
  },
})
Use Sanity UI components (@sanity/ui) for consistent styling with the rest of Studio.
Tools can access all Studio context, including the client, schema, current user, and workspace configuration.
Be cautious when performing mutations or external API calls from tools. Always validate user permissions and handle errors gracefully.

Build docs developers (and LLMs) love