Skip to main content

Overview

The Videos API manages video files, metadata, processing status, and workflow states. Videos belong to projects and support features like public sharing, thumbnails, and time-based commenting. All video functions are defined in convex/videos.ts.

Functions

create

Creates a new video entry in a project.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function UploadButton({ projectId }) {
  const createVideo = useMutation(api.videos.create);
  
  const handleUpload = async (file: File) => {
    const videoId = await createVideo({
      projectId,
      title: file.name,
      description: 'Uploaded video',
      fileSize: file.size,
      contentType: file.type,
    });
    console.log('Created video:', videoId);
  };
  
  return <button onClick={handleUpload}>Upload</button>;
}
projectId
Id<'projects'>
required
The project to create the video in
title
string
required
Video title
description
string
Video description (optional)
fileSize
number
File size in bytes (optional)
contentType
string
MIME type (optional, e.g., “video/mp4”)
videoId
Id<'videos'>
The ID of the created video
Permissions: Requires member role in the project’s team. Implementation: convex/videos.ts:52

list

Lists all videos in a project with comment counts.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function VideoList({ projectId }) {
  const videos = useQuery(api.videos.list, { projectId });
  
  return (
    <div>
      {videos?.map(video => (
        <div key={video._id}>
          <h3>{video.title}</h3>
          <p>{video.workflowStatus}{video.commentCount} comments</p>
        </div>
      ))}
    </div>
  );
}
projectId
Id<'projects'>
required
The project to list videos from
videos
array
Array of video objects with the following fields:
_id
Id<'videos'>
Video ID
title
string
Video title
description
string | undefined
Video description
status
'uploading' | 'processing' | 'ready' | 'failed'
Processing status
workflowStatus
'review' | 'rework' | 'done'
Workflow status (defaults to ‘review’)
visibility
'public' | 'private'
Video visibility
uploaderName
string
Name of the user who uploaded the video
commentCount
number
Number of comments on the video
duration
number | undefined
Video duration in seconds
thumbnailUrl
string | undefined
Thumbnail image URL
muxPlaybackId
string | undefined
Mux playback ID for video streaming
Permissions: Requires access to the project. Implementation: convex/videos.ts:84

get

Retrieves a single video by ID.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function VideoDetail({ videoId }) {
  const video = useQuery(api.videos.get, { videoId });
  
  return (
    <div>
      <h1>{video?.title}</h1>
      <p>{video?.description}</p>
      <p>Your role: {video?.role}</p>
    </div>
  );
}
videoId
Id<'videos'>
required
The video to retrieve
video
object
Video object with all fields plus user’s role
role
'owner' | 'admin' | 'member' | 'viewer'
User’s role in the video’s team
Permissions: Requires access to the video. Implementation: convex/videos.ts:113

getByPublicId

Retrieves a public video by its public ID (for public sharing).
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function PublicVideoPlayer({ publicId }) {
  const result = useQuery(api.videos.getByPublicId, { publicId });
  
  if (!result) return <div>Video not found</div>;
  
  return <VideoPlayer video={result.video} />;
}
publicId
string
required
The public ID of the video
video
object | null
Public video data or null if not found/not public
_id
Id<'videos'>
Video ID
title
string
Video title
muxPlaybackId
string | undefined
Mux playback ID
Permissions: Public - no authentication required. Only returns videos with visibility: 'public' and status: 'ready'. Implementation: convex/videos.ts:126

update

Updates video title and/or description.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function EditVideoForm({ videoId }) {
  const updateVideo = useMutation(api.videos.update);
  
  const handleSubmit = async (title: string, description: string) => {
    await updateVideo({
      videoId,
      title,
      description,
    });
  };
  
  return <form onSubmit={handleSubmit}>...</form>;
}
videoId
Id<'videos'>
required
The video to update
title
string
New title (optional)
description
string
New description (optional)
Permissions: Requires member role in the video’s team. Implementation: convex/videos.ts:202

updateWorkflowStatus

Updates the workflow status of a video.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function WorkflowStatusButtons({ videoId }) {
  const updateStatus = useMutation(api.videos.updateWorkflowStatus);
  
  return (
    <div>
      <button onClick={() => updateStatus({ videoId, workflowStatus: 'review' })}>
        Review
      </button>
      <button onClick={() => updateStatus({ videoId, workflowStatus: 'rework' })}>
        Needs Rework
      </button>
      <button onClick={() => updateStatus({ videoId, workflowStatus: 'done' })}>
        Done
      </button>
    </div>
  );
}
videoId
Id<'videos'>
required
The video to update
workflowStatus
'review' | 'rework' | 'done'
required
The new workflow status
Permissions: Requires member role in the video’s team. Implementation: convex/videos.ts:233

setVisibility

Sets video visibility to public or private.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function VisibilityToggle({ videoId, currentVisibility }) {
  const setVisibility = useMutation(api.videos.setVisibility);
  
  const toggle = () => {
    const newVisibility = currentVisibility === 'public' ? 'private' : 'public';
    setVisibility({ videoId, visibility: newVisibility });
  };
  
  return <button onClick={toggle}>{currentVisibility}</button>;
}
videoId
Id<'videos'>
required
The video to update
visibility
'public' | 'private'
required
The new visibility setting
Permissions: Requires member role in the video’s team. Implementation: convex/videos.ts:219

remove

Deletes a video and all associated data (comments, share links).
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function DeleteVideoButton({ videoId }) {
  const removeVideo = useMutation(api.videos.remove);
  
  const handleDelete = async () => {
    if (confirm('Delete this video?')) {
      await removeVideo({ videoId });
    }
  };
  
  return <button onClick={handleDelete}>Delete</button>;
}
videoId
Id<'videos'>
required
The video to delete
Permissions: Requires admin role in the video’s team. Implementation: convex/videos.ts:247

getVideoForPlayback

Retrieves video data specifically for playback purposes.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function VideoPlayer({ videoId }) {
  const video = useQuery(api.videos.getVideoForPlayback, { videoId });
  
  return <MuxPlayer playbackId={video?.muxPlaybackId} />;
}
videoId
Id<'videos'>
required
The video to retrieve for playback
video
object
Video object with playback information
Permissions: Requires viewer role in the video’s team. Implementation: convex/videos.ts:450

getByShareGrant

Retrieves a video by share grant token (for external reviewers with share links).
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function SharedVideoPlayer({ grantToken }) {
  const result = useQuery(api.videos.getByShareGrant, { grantToken });
  
  if (!result) return <div>Access denied or video not found</div>;
  
  return <VideoPlayer video={result.video} shareLink={result.shareLink} />;
}
grantToken
string
required
The share grant token obtained from issueAccessGrant
video
object | null
Video data or null if grant is invalid/expired
_id
Id<'videos'>
Video ID
title
string
Video title
description
string | undefined
Video description
muxAssetId
string | undefined
Mux asset ID
muxPlaybackId
string | undefined
Mux playback ID for streaming
Share link information
allowDownload
boolean
Whether downloads are allowed
Permissions: Public - requires valid share grant token Implementation: convex/videos.ts:138

getPublicIdByVideoId

Gets the public ID for a video (used for generating public links).
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function ShareButton({ videoId }) {
  const publicId = useQuery(api.videos.getPublicIdByVideoId, { 
    videoId: videoId.toString() 
  });
  
  if (!publicId) return null;
  
  const shareUrl = `https://app.example.com/watch/${publicId}`;
  
  return <button onClick={() => navigator.clipboard.writeText(shareUrl)}>
    Copy Public Link
  </button>;
}
videoId
string
required
The video ID as a string
publicId
string | null
The public ID, or null if video is not public or not ready
Permissions: Requires access to the video Implementation: convex/videos.ts:160

Video Status Flow

Videos progress through the following statuses:
  1. uploading - Video is being uploaded to S3
  2. processing - Video is being processed by Mux
  3. ready - Video is ready for playback
  4. failed - Upload or processing failed

Workflow Status

Videos have an independent workflow status for collaboration:
  • review (default) - Video is ready for review
  • rework - Changes requested
  • done - Video approved

Usage Examples

Creating and Uploading a Video

import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function VideoUploader({ projectId }) {
  const createVideo = useMutation(api.videos.create);
  
  const handleFileSelect = async (file: File) => {
    // Create video entry
    const videoId = await createVideo({
      projectId,
      title: file.name,
      fileSize: file.size,
      contentType: file.type,
    });
    
    // Upload file to S3 (implementation depends on your upload strategy)
    await uploadToS3(file, videoId);
  };
  
  return <input type="file" onChange={(e) => handleFileSelect(e.target.files[0])} />;
}

Video List with Filtering

import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function FilteredVideoList({ projectId }) {
  const allVideos = useQuery(api.videos.list, { projectId });
  const [filter, setFilter] = useState<'all' | 'review' | 'done'>('all');
  
  const videos = allVideos?.filter(v => 
    filter === 'all' || v.workflowStatus === filter
  );
  
  return (
    <div>
      <select value={filter} onChange={(e) => setFilter(e.target.value)}>
        <option value="all">All</option>
        <option value="review">Review</option>
        <option value="done">Done</option>
      </select>
      
      {videos?.map(video => <VideoCard key={video._id} video={video} />)}
    </div>
  );
}

Build docs developers (and LLMs) love