Skip to main content
The Presentation Tool enables side-by-side content editing with live preview of your website or application.

presentationTool

Define and configure a Presentation Tool instance.
import {defineConfig} from 'sanity'
import {presentationTool} from 'sanity/presentation'

export default defineConfig({
  // ...
  plugins: [
    presentationTool({
      previewUrl: 'http://localhost:3000',
    }),
  ],
})

Options

name
string
Tool identifier. Default: 'presentation'
title
string
Tool display title. Default: 'Presentation'
icon
ComponentType
Tool icon component
previewUrl
string | PreviewUrlResolver
required
URL or function that resolves to the preview URL
resolve
object
Configure document and location resolvers
locations
DocumentLocationResolver
Resolve URLs for documents
documents
DocumentResolver[]
Resolve documents from URLs
defaultDocumentType
string
Default document type when creating new documents
components
object
Custom components
header
ComponentType<PreviewHeaderProps>
Custom preview header component

Preview URL Configuration

Static URL

Simple static preview URL:
presentationTool({
  previewUrl: 'http://localhost:3000',
})

Dynamic URL Resolver

Resolve preview URLs based on document:
presentationTool({
  previewUrl: {
    origin: 'http://localhost:3000',
    previewMode: {
      enable: '/api/draft',
    },
    draftMode: {
      enable: '/api/draft',
    },
  },
})
origin
string
required
Base URL of your preview site
previewMode
object
Preview mode configuration
enable
string
API route to enable preview mode
draftMode
object
Draft mode configuration (Next.js)
enable
string
API route to enable draft mode

Document Locations

Resolve where documents appear on your site.

defineLocations

Type helper for defining location resolvers:
import {defineLocations} from 'sanity/presentation'

export const resolve = {
  locations: {
    article: defineLocations({
      select: {
        title: 'title',
        slug: 'slug.current',
      },
      resolve: (doc) => ({
        locations: [
          {
            title: doc?.title || 'Untitled',
            href: `/articles/${doc?.slug}`,
          },
        ],
      }),
    }),
  },
}

export default defineConfig({
  plugins: [
    presentationTool({
      previewUrl: 'http://localhost:3000',
      resolve,
    }),
  ],
})

Location Resolver

select
Record<string, string>
GROQ projection to select document fields
resolve
function
required
Function that returns document locations
doc
SanityDocument
Document to resolve locations for
Returns an object with:
locations
DocumentLocation[]
Array of locations where the document appears
title
string
Location title
href
string
URL path

Document Resolvers

Resolve which documents are on a given page.

defineDocuments

Type helper for defining document resolvers:
import {defineDocuments} from 'sanity/presentation'

export const resolve = {
  documents: defineDocuments([
    {
      route: '/articles/:slug',
      filter: `_type == "article" && slug.current == $slug`,
    },
  ]),
}

export default defineConfig({
  plugins: [
    presentationTool({
      previewUrl: 'http://localhost:3000',
      resolve,
    }),
  ],
})

Document Resolver

route
string
required
URL pattern with parameters (e.g., /posts/:slug)
filter
string
required
GROQ filter to find matching documents. Use $paramName to reference route parameters.

Complete Example

import {defineConfig} from 'sanity'
import {presentationTool, defineLocations, defineDocuments} from 'sanity/presentation'

const resolve = {
  // Define where documents appear
  locations: {
    article: defineLocations({
      select: {
        title: 'title',
        slug: 'slug.current',
        category: 'category->slug.current',
      },
      resolve: (doc) => {
        const locations = []
        
        // Main article page
        if (doc?.slug) {
          locations.push({
            title: doc.title || 'Untitled',
            href: `/articles/${doc.slug}`,
          })
        }
        
        // Category archive
        if (doc?.category) {
          locations.push({
            title: `${doc.title} on category page`,
            href: `/categories/${doc.category}`,
          })
        }
        
        return {locations}
      },
    }),
    
    author: defineLocations({
      select: {
        name: 'name',
        slug: 'slug.current',
      },
      resolve: (doc) => ({
        locations: [
          {
            title: doc?.name || 'Untitled',
            href: `/authors/${doc?.slug}`,
          },
        ],
      }),
    }),
  },
  
  // Define which documents are on a page
  documents: defineDocuments([
    {
      route: '/articles/:slug',
      filter: `_type == "article" && slug.current == $slug`,
    },
    {
      route: '/authors/:slug',
      filter: `_type == "author" && slug.current == $slug`,
    },
    {
      route: '/categories/:slug',
      filter: `_type == "article" && category->slug.current == $slug`,
    },
  ]),
}

export default defineConfig({
  // ...
  plugins: [
    presentationTool({
      previewUrl: {
        origin: 'http://localhost:3000',
        draftMode: {
          enable: '/api/draft',
        },
      },
      resolve,
    }),
  ],
})

Next.js Integration

For Next.js projects with App Router:
// sanity.config.ts
presentationTool({
  previewUrl: {
    origin: process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',
    draftMode: {
      enable: '/api/draft',
    },
  },
  resolve,
})
// app/api/draft/route.ts
import {draftMode} from 'next/headers'
import {redirect} from 'next/navigation'

export async function GET(request: Request) {
  const {searchParams} = new URL(request.url)
  const slug = searchParams.get('slug')
  
  draftMode().enable()
  
  redirect(slug || '/')
}

usePresentationParams

Access presentation parameters in your preview:
import {usePresentationParams} from 'sanity/presentation'

function Preview() {
  const params = usePresentationParams()
  
  console.log(params.preview) // URL being previewed
  console.log(params.viewport) // Viewport size
  
  return <div>Preview</div>
}
params
PresentationParams
Presentation parameters
preview
string
URL being previewed
viewport
'mobile' | 'tablet' | 'desktop'
Current viewport size
perspective
PresentationPerspective
Current perspective (published/previewDrafts)

usePresentationNavigate

Navigate the preview iframe:
import {usePresentationNavigate} from 'sanity/presentation'

function NavigationButton() {
  const navigate = usePresentationNavigate()
  
  return (
    <button onClick={() => navigate('/about')}>
      Go to About
    </button>
  )
}

Build docs developers (and LLMs) love