How to use Sanity client in prepare function for document previews in v3?

29 replies
Last updated: Dec 2, 2025
hey sanity team,
I'm trying to customise some features in v3, specifically with the prepare function on document previews.
With the new approach of removing the part components and implementing hooks to access the sanity client, I am having issues implementing it inside the prepare function, since it is not a component itself. Normally I would find it useful to use the sanity client from within to be able to query the entire project to load data (like to follow a reference in the opposite direction), but now this seems impossible. Something like

preview: {
  select: {
    title: 'title',
    id: '_id'
  },
  prepare: async ({title, id}) => {
    // old approach, some pre-defined client from the parts system
    const {title: parentTitle } = await client.fetch('some query');

    // new approach with useClient hook won't work
    return {
      title: parentTitle + "-" + title,
    }
  }
}
Any suggestions?
AI Update

I understand your frustration! Unfortunately, the prepare function has a significant limitation in Sanity v3: it cannot make custom GROQ queries or access the Sanity client. This is explicitly documented as a limitation of the preview system.

The prepare function only receives the selection object you define in select, and there's no second context parameter with client access. This is by design - the select syntax handles dereferencing, but only in specific, predefined ways.

Your Options

Since you need to query data (like following references in the opposite direction), here are the viable approaches:

Instead of relying on prepare, you can create a custom preview component using the Form Components API. This is a React component where you can use the useClient hook:

import {useClient} from 'sanity'
import {defineType} from 'sanity'
import React from 'react'

const CustomPreview = (props: any) => {
  const client = useClient({apiVersion: '2024-01-01'})
  const [parentTitle, setParentTitle] = React.useState('')

  React.useEffect(() => {
    client.fetch('*[references($id)][0]{title}', {id: props.id})
      .then(result => setParentTitle(result?.title || ''))
      .catch(() => setParentTitle(''))
  }, [props.id, client])

  return props.renderDefault({
    ...props,
    title: parentTitle ? `${parentTitle} - ${props.title}` : props.title
  })
}

export default defineType({
  name: 'myDocument',
  type: 'document',
  components: {
    preview: CustomPreview
  },
  preview: {
    select: {
      title: 'title',
      id: '_id'
    }
  }
})

This approach gives you full React component capabilities, including hooks like useClient, and you can use the renderDefault function to wrap the existing preview behavior while enhancing it with your custom data.

2. Use select with Direct References

If you're following a forward reference (not reverse), you can use dot notation in select to access referenced document fields:

preview: {
  select: {
    title: 'title',
    parentTitle: 'parent.title'  // Works for forward references
  },
  prepare({title, parentTitle}) {
    return {
      title: parentTitle ? `${parentTitle} - ${title}` : title
    }
  }
}

But this only works for references defined on the document, not reverse lookups like your use case requires.

3. Denormalize Your Data

If the relationship is important for previews, consider storing the data you need directly on the document rather than requiring a reverse reference lookup. This isn't always practical, but it's the most performant option since it avoids additional queries.

Why This Changed

In v3, Sanity moved away from the parts system to a more explicit architecture. The prepare function is intentionally kept as a pure transformation function without side effects or async operations. For scenarios requiring data fetching, custom preview components with hooks are the recommended pattern - they're more powerful and align with modern React practices.

The trade-off is that custom components require more code, but they give you full control over the rendering and data fetching logic. The Form Components API provides this flexibility while maintaining type safety and integration with Studio's real-time collaboration features.

Show original thread
29 replies

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?