The Presentation Tool enables side-by-side content editing with live preview of your website or application.
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
Tool identifier. Default: 'presentation'
Tool display title. Default: 'Presentation'
previewUrl
string | PreviewUrlResolver
required
URL or function that resolves to the preview URL
Configure document and location resolversResolve URLs for documents
Resolve documents from URLs
Default document type when creating new documents
Custom componentsCustom 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',
},
},
})
Base URL of your preview site
Preview mode configurationAPI route to enable preview mode
Draft mode configuration (Next.js)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
GROQ projection to select document fields
Function that returns document locationsDocument to resolve locations for
Returns an object with:Array of locations where the document appears
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
URL pattern with parameters (e.g., /posts/:slug)
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>
}
Presentation parametersviewport
'mobile' | 'tablet' | 'desktop'
Current viewport size
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>
)
}