From 18811dca4dd0df1a096ec6b7f9be7aa973fad124 Mon Sep 17 00:00:00 2001 From: Dan Balarin Date: Mon, 26 Jan 2026 15:15:50 +0100 Subject: [PATCH 1/3] feat: add onInit sync --- packages/mux-video/src/hooks/afterDelete.ts | 3 +- packages/mux-video/src/lib/onInitExtension.ts | 76 ++++++++++++++++++- packages/mux-video/src/plugin.ts | 2 +- packages/mux-video/src/types.ts | 11 +++ 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/packages/mux-video/src/hooks/afterDelete.ts b/packages/mux-video/src/hooks/afterDelete.ts index 2d9c988..652c896 100644 --- a/packages/mux-video/src/hooks/afterDelete.ts +++ b/packages/mux-video/src/hooks/afterDelete.ts @@ -14,7 +14,8 @@ const getAfterDeleteMuxVideoHook = (mux: Mux): CollectionAfterDeleteHook => { const response = await mux.video.assets.delete(assetId) } } catch (err: any) { - if (err.type === 'not_found') { + const type = err?.error?.error?.type ?? err?.error?.type ?? err?.type + if (type === 'not_found' || type === 'invalid_parameters') { console.log(`[payload-mux] Asset ${id} not found in Mux, continuing...`) } else { console.error(`[payload-mux] Error deleting asset ${id} from Mux...`) diff --git a/packages/mux-video/src/lib/onInitExtension.ts b/packages/mux-video/src/lib/onInitExtension.ts index 754b3b3..cc0a5e9 100644 --- a/packages/mux-video/src/lib/onInitExtension.ts +++ b/packages/mux-video/src/lib/onInitExtension.ts @@ -1,8 +1,82 @@ +import Mux from '@mux/mux-node' import type { Payload } from 'payload' import type { MuxVideoPluginOptions } from '../types' +import { getAssetMetadata } from './getAssetMetadata' -export const onInitExtension = (pluginOptions: MuxVideoPluginOptions, payload: Payload): void => { +export const onInitExtension = async ( + pluginOptions: MuxVideoPluginOptions, + payload: Payload, + mux: Mux, +): Promise => { try { + if (pluginOptions.onInitBehavior === 'none') { + return + } + + const videos = await mux.video.assets.list() + const ids = videos.data.map((video) => video.id) + const existingVideos = await payload.find({ + collection: (pluginOptions.extendCollection as string) ?? 'mux-video', + where: { + assetId: { + in: ids, + }, + }, + limit: videos.data.length, + }) + + const collection = (pluginOptions.extendCollection as string) ?? 'mux-video' + const shouldCreate = + pluginOptions.onInitBehavior === 'createOnly' || + pluginOptions.onInitBehavior === 'createAndDelete' + const shouldDelete = + pluginOptions.onInitBehavior === 'deleteOnly' || + pluginOptions.onInitBehavior === 'createAndDelete' + + if (shouldCreate) { + const missingVideos = videos.data.filter( + (video) => !existingVideos.docs.find((doc) => doc.assetId === video.id), + ) + payload.logger.info( + `[payload-mux] Creating missing Mux video entries (${missingVideos.length})...`, + ) + + for (const video of missingVideos) { + await payload.create({ + collection, + data: { + title: `Video ${video.id}`, + assetId: video.id, + ...video, + ...getAssetMetadata(video), + }, + }) + } + } + + if (shouldDelete) { + const extraVideos = await payload.find({ + collection: (pluginOptions.extendCollection as string) ?? 'mux-video', + limit: 100, + where: { + assetId: { + not_in: ids, + }, + }, + }) + payload.logger.info( + `[payload-mux] Deleting extra Mux video entries (${extraVideos.docs.length})...`, + ) + + for (const video of extraVideos.docs) { + if (pluginOptions.onInitBehavior === 'createAndDelete') { + await payload.delete({ + collection, + id: video.id, + }) + } + } + } } catch (err: unknown) { payload.logger.error({ err, msg: 'Error in onInitExtension' }) } diff --git a/packages/mux-video/src/plugin.ts b/packages/mux-video/src/plugin.ts index 346c852..fb48c7f 100644 --- a/packages/mux-video/src/plugin.ts +++ b/packages/mux-video/src/plugin.ts @@ -74,7 +74,7 @@ export const muxVideoPlugin = await incomingConfig.onInit(payload) } - onInitExtension(pluginOptions, payload) + await onInitExtension(pluginOptions, payload, mux) } return config diff --git a/packages/mux-video/src/types.ts b/packages/mux-video/src/types.ts index b3bbf6b..e365d5f 100644 --- a/packages/mux-video/src/types.ts +++ b/packages/mux-video/src/types.ts @@ -121,6 +121,17 @@ export type MuxVideoPluginOptions = { */ animatedGifExtension?: 'gif' | 'webp' + /** + * What to do with mismatching videos on initialization. + * - `"createOnly"`: Create entries for videos that do not exist in the Payload collection. + * - `"deleteOnly"`: Delete entries for videos that no longer exist on Mux. + * - `"createAndDelete"`: Create entries for missing videos and delete entries for videos that no longer exist on Mux. + * - `"none"`: Do nothing on initialization. + * + * @default "none" + */ + onInitBehavior?: "createOnly" | "deleteOnly" | "createAndDelete" | "none" + /** * An optional function to determine whether the current request is allowed to upload files. * Should return a boolean or a Promise resolving to a boolean. From bcf8794c7aeb1dc0736e2c5bdf13972f70c99fdf Mon Sep 17 00:00:00 2001 From: Dan Balarin Date: Fri, 6 Mar 2026 12:37:15 +0100 Subject: [PATCH 2/3] style: clean up onInit function --- packages/mux-video/src/lib/onInitExtension.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/mux-video/src/lib/onInitExtension.ts b/packages/mux-video/src/lib/onInitExtension.ts index cc0a5e9..ef7f311 100644 --- a/packages/mux-video/src/lib/onInitExtension.ts +++ b/packages/mux-video/src/lib/onInitExtension.ts @@ -15,8 +15,10 @@ export const onInitExtension = async ( const videos = await mux.video.assets.list() const ids = videos.data.map((video) => video.id) + const collection = (pluginOptions.extendCollection as string) ?? 'mux-video' + const existingVideos = await payload.find({ - collection: (pluginOptions.extendCollection as string) ?? 'mux-video', + collection, where: { assetId: { in: ids, @@ -25,7 +27,6 @@ export const onInitExtension = async ( limit: videos.data.length, }) - const collection = (pluginOptions.extendCollection as string) ?? 'mux-video' const shouldCreate = pluginOptions.onInitBehavior === 'createOnly' || pluginOptions.onInitBehavior === 'createAndDelete' @@ -56,7 +57,7 @@ export const onInitExtension = async ( if (shouldDelete) { const extraVideos = await payload.find({ - collection: (pluginOptions.extendCollection as string) ?? 'mux-video', + collection, limit: 100, where: { assetId: { From fea4a29ca9fc090995450d9b6d269ae710f6e638 Mon Sep 17 00:00:00 2001 From: Dan Balarin Date: Fri, 6 Mar 2026 12:41:55 +0100 Subject: [PATCH 3/3] docs: update documentation for onInit and extensions --- packages/mux-video/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/mux-video/README.md b/packages/mux-video/README.md index 4f5e2c8..6e15167 100644 --- a/packages/mux-video/README.md +++ b/packages/mux-video/README.md @@ -80,6 +80,9 @@ export default buildConfig({ | `access` | `(request: PayloadRequest) => Promise \| boolean` | *Optional* | An optional function to determine who can upload files. Should return a boolean or a Promise resolving to a boolean. | | `signedUrlOptions` | `MuxVideoSignedUrlOptions` | *Optional* | Options for signed URL generation. | | `adminThumbnail` | `'gif' \| 'image' \| 'none'` | `"gif"` | Specifies the type of thumbnail to display for videos in the collection list view. | +| `posterExtension` | `'jpg' \| 'png' \| 'webp'` | `"png"` | The file extension to use for generated poster images. | +| `animatedGifExtension` | `'gif' \| 'webp'` | `"gif"` | The file extension to use for generated animated gifs. | +| `onInitBehavior` | `"createOnly" \| "deleteOnly" \| "createAndDelete" \| "none"` | `"none"` | What to do on plugin initialization when there are discrepancies between videos in Mux and entries in the Payload collection. | ### `initSettings` Options