Sanity.io is a headless CMS that enables structured content modeling through schema definitions written in JavaScript for the creation of reusable and modular content types. It supports storing detailed video metadata, referencing hosted video assets, and establishing relationships between content items for complex use cases. Video assets and their metadata can be uploaded, managed, and queried using GROQ, Sanity"s purpose-built query language designed for flexible and precise retrieval of structured content.

Defining a Video Schema in Sanity

Sanity schemas are defined using JavaScript objects. A basic video content model typically includes fields for title, description, video file reference, and optionally a thumbnail or duration.

Example:

code
export default {
name: 'video',
title: 'Video',
type: 'document',
fields: [
{ name: 'title', type: 'string' },
{ name: 'description', type: 'text' },
{ name: 'videoFile', type: 'file' },
{ name: 'thumbnail', type: 'image' },
{ name: 'duration', type: 'number' }
]
}

Explanation:

  • videoFile stores the actual video file.
  • thumbnail is an optional image field used for preview purposes.
  • duration can be added manually or generated during processing.

Uploading Video Files in Sanity Studio

Sanity Studio provides a simple interface for uploading and managing video files. By selecting the appropriate document type and using the file upload field, you can add video content in common formats. Once uploaded, the file is stored in Sanity"s asset system with metadata that can be used for display, organization, or querying within your application.

  • Open Sanity Studio and navigate to the "Video" document type.
  • Use the file picker to upload supported formats like .mp4, .mov, or .webm.
  • Sanity stores the file in its asset store and generates metadata such as URL, size, and MIME type.

Each uploaded video file is assigned a unique _id and can be referenced in queries and frontend applications.

Best Practices for Video Uploads

  • Compress Before Uploading (Use tools like HandBrake or FFmpeg).
  • Use External Hosting for Large Files (e.g., Mux, Cloudflare Stream, or AWS S3).
  • Set Up Webhooks for Processing (Generate thumbnails, extract duration automatically).
Video Uploading

Example: Auto-Generate Thumbnails with a Script

If you need dynamic thumbnails, use Sanity"s JavaScript client:

code
import sanityClient from '@sanity/client';

const client = sanityClient({
projectId: 'your-project-id',
dataset: 'production',
token: 'your-write-token'
});

// After upload, run this to create a thumbnail (pseudo-code)
async function generateThumbnail(videoId) {
// Use FFmpeg or a service like Mux
const thumbnailUrl = await extractThumbnail(videoId);

await client.patch(videoId)
.set({ thumbnail: { _type: 'image', asset: { _ref: thumbnailUrl } } })
.commit();
}

Explanation:

  • Sanity client is initialized using your project credentials to enable write operations to the dataset.
  • generateThumbnail function extracts a thumbnail (e.g., via FFmpeg or Mux) and updates the video document in Sanity by setting the thumbnail field with the image reference.

Accessing Video URLs from Sanity Data

When querying video documents, Sanity returns a file object that includes the asset reference. You need to resolve this using the urlFor() utility or manually construct the URL from the asset ID.

Example GROQ Query:

code
*[_type == "video"]{
title,
"videoUrl": videoFile.asset->url
}

Explanation :

  • *[_type == "video"] : This filters all documents of type "video" in the Sanity dataset.
  • "videoUrl": videoFile.asset->url : This line accesses the referenced video file asset and extracts its url, renaming it as videoUrl in the query result.

Displaying Videos in the Frontend

To render videos stored in Sanity, use the resolved video URL in your frontend application. Frameworks like React or Next.js allow you to embed videos using the native HTML <video> element. By placing the videoUrl inside the src attribute of the <source> tag, the browser can stream and play the video directly.

Example JSX:

code
<video controls>
<source src={videoUrl} type="video/mp4" />
</video>

The controls attribute enables built-in playback options like play, pause, and volume. This setup allows seamless integration of Sanity-hosted videos into client-side components.

Referencing Videos in Other Content Types

Sanity allows you to create relationships between different content types using references. This is useful when you want to associate a video with another document"such as embedding a video in a blog post, lesson, or product page"without duplicating the video data.

Example Field in Another Schema:

code
{
name: 'videoReference',
type: 'reference',
to: [{ type: 'video' }]
}

By adding a reference field like this, you can link a single video document to multiple other documents. This approach improves content reusability and makes updates easier, since changes to the video document are reflected wherever it"s referenced.

Querying Referenced Videos with GROQ

When a video is referenced in another document, such as a lesson or blog post, you can use GROQ to follow that reference and extract fields from the linked video document. This is done using the -> dereferencing operator, which allows you to access nested data from the target document.

Example:

code
*[_type == "lesson"]{
title,
videoReference->{
title,
"url": videoFile.asset->url
}
}

In this query:

  • It selects all documents of type "lesson".
  • It retrieves the lesson's title.
  • It follows the videoReference to get the referenced video's title and url.

This approach is efficient for displaying related video content directly alongside other structured data in your frontend.

Structuring Video Metadata

To make your video content more searchable and organized, you can extend the video schema with additional metadata fields. These fields support advanced filtering, categorization, and content display logic in frontend applications.

Optional Fields:

  • categories: An array field used to assign tags or references for grouping videos by topic, genre, or purpose.
  • resolution: A string or number field (e.g., "1080p") that helps identify the video quality or format.
  • isFeatured: A boolean value used to flag specific videos for special placement, such as highlighting on the homepage or in a curated list.

Adding these fields gives you more flexibility when building filters, organizing media, or applying conditional rendering based on metadata.

Best Practices

  1. Use File Type Validation In your schema, restrict uploads to video MIME types to prevent incorrect uploads.
  2. Reference Instead of Embedding Use references for reusability instead of duplicating video fields in multiple content types.
  3. Organize Videos by Purpose Maintain consistent naming and use fields like category or tags for better organization.
  4. Avoid Oversized Uploads in Studio For large files, consider uploading externally and storing URLs in a url field instead of using the file upload field.