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>;
}
The project to create the video in
Video description (optional)
File size in bytes (optional)
MIME type (optional, e.g., “video/mp4”)
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>
);
}
The project to list videos from
Array of video objects with the following fields:status
'uploading' | 'processing' | 'ready' | 'failed'
Processing status
workflowStatus
'review' | 'rework' | 'done'
Workflow status (defaults to ‘review’)
Name of the user who uploaded the video
Number of comments on the video
Video duration in seconds
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>
);
}
Video object with all fields plus user’s rolerole
'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} />;
}
The public ID of the video
Public video data or null if not found/not public
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>;
}
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>
);
}
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>;
}
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>;
}
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} />;
}
The video to retrieve for playback
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} />;
}
The share grant token obtained from issueAccessGrant
Video data or null if grant is invalid/expiredMux playback ID for streaming
Share link informationWhether 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>;
}
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:
- uploading - Video is being uploaded to S3
- processing - Video is being processed by Mux
- ready - Video is ready for playback
- 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>
);
}