
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeI can see the issue! In Sanity v3, the way you define custom preview components has changed. You're mixing the old component property with the preview.prepare() method, which doesn't work together properly.
The Problem:
In v3, when you want a custom preview component, you need to use components.preview (not preview.component). Also, when you define a custom preview component, it receives specific props from the Form Components API, not the props you return from prepare().
The Solution:
You have two options depending on what you want to achieve:
Option 1: Simple thumbnail preview (recommended for most cases)
If you just want to show the YouTube thumbnail in lists and references, stick with the standard preview configuration without a custom component:
import { defineType } from "sanity";
import getYouTubeId from "get-youtube-id";
export default defineType({
name: "youtube",
type: "object",
fields: [
{
name: "url",
title: "URL",
type: "url",
validation: (Rule) => Rule.required(),
},
],
preview: {
select: {
url: "url",
},
prepare(selection) {
const { url } = selection;
if (!url) {
return {
title: "Missing YouTube URL",
};
}
const id = getYouTubeId(url);
return {
title: "YouTube Video",
subtitle: url,
media: id ? (
<img
src={`https://i.ytimg.com/vi/${id}/hqdefault.jpg`}
alt="Video thumbnail"
/>
) : undefined,
};
},
},
});Option 2: Full custom preview component with embedded video
If you want the actual embedded video player to show in the preview, use components.preview. Note that the custom component receives different props than what prepare() returns - it gets the entire document value:
import { defineType } from "sanity";
import getYouTubeId from "get-youtube-id";
import { PreviewProps } from "sanity";
const YoutubePreview = (props: PreviewProps) => {
const { title } = props;
const url = (props as any).value?.url; // Access the actual field value
if (!url) {
return <div>Missing YouTube URL</div>;
}
const id = getYouTubeId(url);
const embedUrl = `https://www.youtube.com/embed/${id}`;
return (
<div style={{ padding: '1em' }}>
<h3>{title || 'YouTube Video'}</h3>
<iframe
width="100%"
height="315"
src={embedUrl}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</div>
);
};
export default defineType({
name: "youtube",
type: "object",
fields: [
{
name: "url",
title: "URL",
type: "url",
validation: (Rule) => Rule.required(),
},
],
components: {
preview: YoutubePreview // Use components.preview, not preview.component
},
preview: {
select: {
url: "url",
},
},
});Key differences in v3:
components.preview instead of preview.component (the old v2 way)PreviewProps which includes the document value, not the result of prepare()prepare() function is still useful for standard previews (Option 1), but isn't used when you have a custom componentOption 1 is usually better for performance since it just shows a thumbnail, while Option 2 embeds the full video player which might slow down your Studio if you have many videos in a list.
Hope this helps! 🎥
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store