The Mux Convex component syncs your Mux assets, live streams, and uploads to your Convex database with real-time updates via webhooks.
The @mux/convex package is a reusable Convex component that bridges Mux video services with the Convex backend. It provides database tables for Mux resources, webhook handling, and query helpers for building video applications.
After setup, your Convex database will have tables for:
Before you begin, make sure you have:
convex/ directory already initializednpm install -g convex)New to Convex? Follow the Convex quickstart for your framework first, then come back here to add Mux integration.
Install the required packages in your project:
npm install convex @mux/convex @mux/mux-nodeRun the initialization script to generate the required Convex files:
npx @mux/convex init --component-name muxThis creates four files in your convex directory:
convex.config.ts - Mounts the Mux component into your Convex appmigrations.ts - Backfill function for syncing existing Mux assetsmuxWebhook.ts - Webhook handler for receiving Mux eventshttp.ts - HTTP route that exposes the webhook endpointIf any of these files already exist, the CLI will skip them. Use --force to overwrite existing files.
If you already have a convex/convex.config.ts or convex/http.ts, use --skip-config or --skip-http to skip those files and manually integrate the Mux component into your existing configuration.
Configure your Mux API credentials in Convex:
npx convex env set MUX_TOKEN_ID your-mux-token-id
npx convex env set MUX_TOKEN_SECRET your-mux-token-secretYou can find your API credentials in the Mux Dashboard.
Start the Convex development server:
npx convex devIn another terminal, run the backfill migration to sync your existing Mux assets:
npx convex run migrations:backfillMux '{}'This will import your existing Mux assets into the Convex assets and videoMetadata tables. You can customize the backfill with options:
npx convex run migrations:backfillMux '{"maxAssets": 500, "defaultUserId": "my-user-id", "includeVideoMetadata": true}'The backfill currently syncs Mux assets only. Live streams and uploads will be synced in real time once you configure the webhook in the next step.
In the Mux Dashboard, create a new webhook with your Convex HTTP endpoint as the URL:
https://your-deployment.convex.site/mux/webhookMux will send all events to this endpoint. The component automatically handles routing asset, live stream, and upload events to the appropriate tables and ignores unsupported event types.
After creating the webhook, copy the signing secret and add it to your Convex environment:
npx convex env set MUX_WEBHOOK_SECRET your-webhook-signing-secretCheck your Convex dashboard to verify the tables are populated:
assets, uploads, liveStreams, events, and videoMetadataThe component syncs Mux data into your Convex database via webhooks. To access that data, you wrap the component's built-in queries with your own Convex functions, then call those functions from your frontend.
Create a new file in your convex directory (e.g., convex/videoQueries.ts) to wrap the component queries:
import { query } from './_generated/server';
import { components } from './_generated/api';
import { v } from 'convex/values';
// Returns an array of asset objects, each containing:
// muxAssetId, status, playbackIds, durationSeconds, aspectRatio, tracks, etc.
export const listAssets = query({
handler: async (ctx) => {
return await ctx.runQuery(components.mux.catalog.listAssets, {});
},
});
// Returns a single asset object or null
export const getAsset = query({
args: { muxAssetId: v.string() },
handler: async (ctx, args) => {
return await ctx.runQuery(components.mux.catalog.getAssetByMuxId, {
muxAssetId: args.muxAssetId,
});
},
});
// Returns an array of { metadata, asset } pairs for a given user
export const getUserVideos = query({
args: { userId: v.string() },
handler: async (ctx, args) => {
return await ctx.runQuery(components.mux.videos.listVideosForUser, {
userId: args.userId,
});
},
});Call these queries from your React components using Convex's useQuery hook:
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';
function VideoList() {
const assets = useQuery(api.videoQueries.listAssets);
if (!assets) return <div>Loading...</div>;
return (
<ul>
{assets.map((asset) => (
<li key={asset.muxAssetId}>
{asset.muxAssetId} — {asset.status}
</li>
))}
</ul>
);
}These queries are reactive — your UI will update automatically when the underlying data changes in Convex, such as when a webhook updates an asset's status.
The component exposes the following queries through components.mux:
| Query | Description |
|---|---|
catalog.listAssets | List all synced assets, sorted by most recently updated |
catalog.getAssetByMuxId | Get a single asset by its Mux asset ID |
catalog.listLiveStreams | List all synced live streams |
catalog.getLiveStreamByMuxId | Get a single live stream by its Mux live stream ID |
catalog.listUploads | List all synced direct uploads |
catalog.getUploadByMuxId | Get a single upload by its Mux upload ID |
catalog.listRecentEvents | List recent webhook events |
videos.listVideosForUser | List assets with app-level metadata for a specific user |
videos.getVideoByMuxAssetId | Get an asset with its associated metadata |
All list queries accept an optional limit parameter (default: 50).
The catalog queries above return Mux-level data synced via webhooks. The video metadata system lets you layer your own app-level data — like titles, descriptions, user ownership, and visibility — on top of those synced assets.
import { mutation } from './_generated/server';
import { components } from './_generated/api';
import { v } from 'convex/values';
export const setVideoMetadata = mutation({
args: {
muxAssetId: v.string(),
userId: v.string(),
title: v.optional(v.string()),
description: v.optional(v.string()),
visibility: v.optional(
v.union(v.literal('public'), v.literal('private'), v.literal('unlisted'))
),
tags: v.optional(v.array(v.string())),
},
handler: async (ctx, args) => {
await ctx.runMutation(components.mux.videos.upsertVideoMetadata, {
muxAssetId: args.muxAssetId,
userId: args.userId,
title: args.title,
description: args.description,
visibility: args.visibility,
tags: args.tags,
});
},
});Once metadata is set, queries like videos.listVideosForUser and videos.getVideoByMuxAssetId will return both the Mux asset data and your app-level metadata together.
The backfill is a one-time synchronization. After that, webhooks maintain near real-time updates between Mux and Convex.
Make sure to use consistent component names across all configuration steps. If you change the component name from mux, regenerate the wrapper files with the matching name: npx @mux/convex init --component-name yourName --force