How to build an input component for Sanity Studio v3
This guide teaches how to add a custom input component to a field for Sanity Studio v3
Go to How to build an input component for Sanity Studio v3How to add a YouTube embed in the Studio, and render it on frontends
There are times when you want to embed a video in your rich text content. Usually, you would paste the HTML embed code into the editor and move along. With Portable Text, however, you want to make sure that your content is structured and you don't embed too many assumptions about the presentation in it. Maybe you want to use this video in a native app or instead use a specialized component for it in your frontend(s).
In this tutorial, we will cover how to add a custom YouTube block into your schema and the editor, how to add a custom preview component for it, and how to serialize it in the frontend. We will also look at how you can add a custom paste handler so that your editors can copy-paste a YouTube link right into the editor and have it stored as the custom block.
The main thing we are interested in when doing a YouTube embed is not the embed code in itself but a way to identify what video it is, that is, its ID. The ID is included in the video URL, so if we for example have this URL, https://www.youtube.com/watch?v=asENbq3s1m8&feature=youtu.be
, the ID is asENbq3s1m8
.
Now, we could have asked our editors to find this ID and only put that into Sanity, but that seems a bit too much to ask and isn't always easy since YouTube URLs can contain a lot of parameters. We will assume that we can work with an URL and find the ID programmatically wherever we want to show the video.
We'll make a very simple schema, to begin with. An object we call, with a field called url
. You can extend this later if you want to for example, specify a time stamp or other things.
// youtube.js
export default {
name: 'youtube',
type: 'object',
title: 'YouTube Embed',
fields: [
{
name: 'url',
type: 'url',
title: 'YouTube video URL'
}
]
}
The next step is to import the file into the schemas/index.js
to make it available as a type: 'youtube'
:
// schemas/index.js
import blockContent from './blockContent'
import category from './category'
import post from './post'
import author from './author'
import youtube from './youtube'
export const schemaTypes = [post, author, category, blockContent, youtube]
Now we can include this field in bodyPortableText:
// bodyPortableText.js
export default {
name: 'bodyPortableText',
type: 'array',
title: 'Body',
of: [
{
type: 'block'
},
{
type: 'youtube'
}
]
}
Now you can access the new YouTube field in the editor’s toolbar, where you can paste in a YouTube URL. However, it would be nice to get an actual preview of the video that you want to embed. So let’s add that.
You’re maybe already familiar with how to configure previews with Sanity. First, you select
the fields you want to get content from. You can assign them to variables like title
, subtitle
, and media
 and let the Studio figure out the rest. Or you can return a prepare
function if you want more control over what goes into the slots.
However, you can also pass a React component and gain full control over what’s rendered. It still gets the variables that you define in select
 as props. For the YouTube preview, we’re going to use a pre-built component called react--lite-youtube
, as well as a package called get-youtube-id
 first to get the ID from the URL, and return and render the embed in the editor.
We start by installing the dependencies we need by running npm install react-lite-youtube get-youtube-id
 in the root folder. Then we open the youtube.js
 file and add the following to it:
// youtube.js
import getYouTubeId from 'get-youtube-id'
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css'
const Preview = (props) => {
const {url, renderDefault} = props
if (!url) {
return <div>Missing YouTube URL</div>
}
const id = getYouTubeId(url)
return (
<div>
// 👇 Renders the default preview UI
{renderDefault({...props, title: 'YouTube Embed'})}
// 👇 Renders the video preview below
<LiteYouTubeEmbed id={id} />
</div>
)
}
export default {
name: 'youtube',
type: 'object',
title: 'YouTube Embed',
fields: [
{
name: 'url',
type: 'url',
title: 'YouTube video URL',
},
],
preview: {
select: {
url: 'url',
},
},
components: {
preview: Preview,
},
}
This pretty much mirrors what you would do in a React front as well. And speaking of frontends, let's take a closer look at how this should be implemented.
When you insert a YouTube embed in the rich text editor, it will be added as a custom block in the Portable Text array. If you are not familiar with how Portable Text works, you can learn more in this introduction. You can also tag along with this tutorial and follow the code examples if you just want to get a feel for it.
To render custom blocks, as the YouTube embed is, you have to add a serializer to the Portable Text package for React (currently called @portabletext/react
). If you don't have this in your project from before, you'll have to install it by running npm install @portabletext/react
in your command line.
Next up is installing a component to show the YouTube video, and a package to suss out the id for the YouTube video from its URL. In this case we’re going to install the same dependencies, and add pretty much the same code, as we did in the studio. So first, run npm install react-youtube get-youtube-id
 to add the dependencies in your frontend project.
And where you want to be able to output this YouTube embed, do the following:
import React from 'react'
import getYouTubeId from 'get-youtube-id'
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css'
import {PortableText} from '@portabletext/react'
const serializers = {
types: {
youtube: ({node}) => {
const { url } = node
const id = getYouTubeId(url)
return (<LiteYouTubeEmbed id={id} />)
}
}
}
export default function Body ({blocks}) {
return (
<PortableText value={blocks} types={serializers} />
)
}
To render custom blocks, as the YouTube embed is, you have to add a serializer to the Portable Text package for Vue (currently called sanity-block-vue-component
). If you don't have this in your project from before, you'll have to install it by running npm installl sanity-block-vue-component
in your command line. We need something for YouTube as well, so let's run npm install vue-youtube
and add it to where we mount our Vue app:
import Vue from "vue";
import App from "./App.vue";
import VueYoutube from "vue-youtube";
Vue.use(VueYoutube);
new Vue({
render: h => h(App)
}).$mount("#app");
Then we'll create a new YouTube.vue
file for our component with the code we need to render the video. We'll add the same package as we used in the studio to get the ID from the URL (npm install get-youtube-id
):
<template>
<youtube :video-id="videoId"></youtube>
</template>
<script>
import getId from "get-youtube-id";
export default {
data() {
return {
videoId: getId(this.url)
};
},
props: {
url: {
type: String,
default: () => ""
}
}
};
</script>
In our component for Portable Text we can now import YouTube.vue
and add it to our serializers
, like this:
<template>
<PortableText :blocks="blocks" :serializers="serializers" />
</template>
<script>
import PortableText from "sanity-blocks-vue-component";
import Youtube from './YouTube'
export default {
components: {
PortableText
},
props: {
blocks: {
type: Array,
default: () => []
}
},
data() {
return {
serializers: {
types: {
youtube: YouTube
}
}
};
}
};
</script>
That's it! Now the data under node
in the custom YouTube block in our Portable Text array will be passed on as props to the YouTube.vue
component.
This guide teaches how to add a custom input component to a field for Sanity Studio v3
Go to How to build an input component for Sanity Studio v3A thorough intro to using GROQ-projections in a webhook contest
Go to GROQ-Powered Webhooks – Intro to ProjectionsA thorough intro to using GROQ-filters in a webhook-context
Go to GROQ-Powered Webhooks – Intro to FiltersLearn how to implement live content previews with Next.js and Sanity Studio
Go to Live Preview with Next.js and Sanity Studio: A Complete Guide