Custom YouTube preview component not working in Sanity v3
I 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:
- Use
components.previewinstead ofpreview.component(the old v2 way) - Custom preview components receive
PreviewPropswhich includes the documentvalue, not the result ofprepare() - The
prepare()function is still useful for standard previews (Option 1), but isn't used when you have a custom component
Option 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! π₯
Show original thread3 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.