Custom YouTube preview component not working in Sanity v3
Im trying to make a custom preview of a embed youtube video but it doesnt work. What am i doing wrong? Its version 3.
import { defineField, defineType } from "sanity";
import getYouTubeId from "get-youtube-id";
interface Youtube {
_type: "youtube";
url: string;
}
interface PreviewProps {
media: React.ReactNode;
title: string;
}
const YoutubePreview: React.FC<PreviewProps> = ({ media, title }) => {
const id = getYouTubeId(media as string);
const embedUrl = `<https://www.youtube.com/embed/${id}>`;
return (
<div>
<h2>{title}</h2>
<iframe
width="560"
height="315"
src={embedUrl}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
);
};
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",
media: <div>Missing YouTube URL</div>,
};
}
const id = getYouTubeId(url);
const media = (
<img
src={`<https://i.ytimg.com/vi/${id}/hqdefault.jpg>`}
alt="Video thumbnail"
/>
);
return {
title: "YouTube Video",
media,
};
},
component: YoutubePreview,
},
}) as unknown as Youtube;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
Was this answer helpful?
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.