Mux Video Uploader

Mux Video Uploader

By Erik Peña

A plugin for uploading video and audio files to Mux and managing them within Strapi.

Latest version

v3.2.0

released October 22, 2026

npm install strapi-plugin-mux-video-uploader

Mux Video Uploader

A Strapi plugin for managing uploads to Mux.

This plugin provides the ability to upload content via a url or a direct file upload to Mux.

🤩 Features

  • Upload videos using a url or a file to Mux from inside of Strapi
  • Manage assets with the plugin's asset grid and pagination capabilities
  • Search for assets using title or Mux Asset ID values
  • Preview content using a player (powered by the mux-player-react project)
  • Delete assets which result in the Mux Asset also being deleted
  • Support uploading audio files
  • Attachment of either sidecar subtitle files (supports .vtt or .srt files) or Mux's auto-generated captions
  • Enable Signed playback for protected video delivery
  • Support of Mux's video quality and maximum stream resolution
  • Enable MP4 downloads on assets during upload
  • Mux updates Strapi automatically when the asset is ready using Webhooks
  • Run Mux Robots AI workflows (summaries, captions, translations, chapters, moderation and more) from a video's details, and attach Robots directives to new uploads
  • Apply a video's generated title, description and tags to the entries that use it, from the Content Manager

🧑‍💻 Install

For installing with Strapi v5, install the latest—

npm i strapi-plugin-mux-video-uploader@latest

yarn add strapi-plugin-mux-video-uploader@latest

For installing with Strapi v4, install v2.0.0—

npm i strapi-plugin-mux-video-uploader@2.8.4

yarn add strapi-plugin-mux-video-uploader@2.8.4

🖐 Requirements

  • A Mux account
  • You will need both the Access Token and Secret Key scoped with "Full Access" permissions which can be created in the Mux Dashboard
  • (Optional) If you plan to support Signed video playback, you will need to obtain a Signing key set
  • The Webhook Signing Secret which can be created in the Mux Dashboard (See the Webhooks section for more info)
  • (Optional) To use Mux Robots, the access token also needs the Robots permission (robots:*). See Mux Robots
  • Tested with Strapi v5.0.6 Community Edition

⚙️ Configuration

In order for this plugin to communicate with Mux, some configuration values need to be set for the plugin before it can be used.

Starting in v3.0.0 of this plugin, we have switch to Strapi's File Based Config. To increase the portability of clustered deployments, we have switched to this paradigm to manage app configs. This means that setting the configurations using the Settings view in the Strapi Admin UI will no longer be available.

In migrating to v3.0.0, you will need to transition to the Strapi File Based Config and copy the values that you had used to initially set up your plugin—

Typescript - Plugin Config

// Path: ./config/plugins.ts
export default ({env}) => ({
  // ...
  "mux-video-uploader": {
    enabled: true,
    config: {
      accessTokenId: env('ACCESS_TOKEN_ID'),
      secretKey: env('ACCESS_TOKEN_SECRET'),
      webhookSigningSecret: env('WEBHOOK_SIGNING_SECRET'),
      playbackSigningId: env('SIGNING_KEY_ID'),
      playbackSigningSecret: env('SIGNING_KEY_PRIVATE_KEY'),
      defaultDirectiveIds: env.array('MUX_DEFAULT_DIRECTIVE_IDS', []),
    }
  }
  // ...
});

Javascript - Plugin Config

// Path: ./config/plugins.js
module.exports = ({env}) => ({
  // ...
  "mux-video-uploader": {
    enabled: true,
    config: {
      accessTokenId: env('ACCESS_TOKEN_ID'),
      secretKey: env('ACCESS_TOKEN_SECRET'),
      webhookSigningSecret: env('WEBHOOK_SIGNING_SECRET'),
      playbackSigningId: env('SIGNING_KEY_ID'),
      playbackSigningSecret: env('SIGNING_KEY_PRIVATE_KEY'),
      defaultDirectiveIds: env.array('MUX_DEFAULT_DIRECTIVE_IDS', []),
    }
  }
  // ...
});
  • webhookSigningSecret must be set, or every webhook is rejected. See Webhooks.
  • defaultDirectiveIds is optional: the Mux Robots directives to run on every new upload (see Directives on upload). Set MUX_DEFAULT_DIRECTIVE_IDS to a comma-separated list of directive ids, such as drv_abc123,drv_def456. Leave it empty or unset for none: the plugin trims each entry and ignores empty ones.

🪝 Webhooks

Mux webhooks tell Strapi when a video is ready, and bring Mux Robots results: jobs, directive runs, their outputs, and the new captions or chapters they add to a video.

  1. In the Mux Dashboard, create a webhook whose "URL to notify" is—

    {STRAPI_BASE_URL}/mux-video-uploader/webhook-handler

    Where {STRAPI_BASE_URL} is the publicly accessible hostname of your Strapi instance.

  2. Copy that webhook's signing secret into webhookSigningSecret, and restart Strapi.

webhookSigningSecret must be set. The plugin checks every webhook's signature with it. If it's empty or wrong, every webhook is rejected with a 401 and new uploads stay in "Preparing"; if it's empty, Strapi also logs a warning when it starts. Once it's right, resend the rejected events from Mux (for example with mux webhooks events replay) and the videos catch up.

Leave includeUnparsed on. Checking a signature needs the raw request body, so the plugin turns on includeUnparsed for strapi::body in your config/middlewares file. If you set includeUnparsed: false there, the plugin warns when Strapi starts, and rejects webhooks with a 500 and an error in the log. Keeping the raw body costs memory for the length of each JSON or form request; nothing else about request parsing changes.

Local development

The Mux CLI can forward webhooks to a local Strapi. It prints a signing secret to use as webhookSigningSecret—

mux webhooks listen --forward-to http://localhost:1337/mux-video-uploader/webhook-handler

🤖 Mux Robots

Mux Robots runs AI workflows on your Mux videos. Each run consumes Mux AI units: see Robots pricing.

Set up Robots

  1. Make sure Robots is enabled for your Mux account.
  2. Create a new access token with the Robots permission (robots:*) in the Mux Dashboard, and use it for accessTokenId / secretKey. Existing tokens can't gain new permissions.
  3. Set up Webhooks: they're how results reach Strapi.
  4. Grant Run Robots to the roles that should run workflows (see Permissions below).
  5. Optionally, set up directives to run on every upload.

Run workflows and directives

The Robots tab of a video's details lets you:

  • run any of the 12 workflows, or a directive;
  • cancel a running job;
  • see every job and directive run on that video, with its output and the Mux AI units it consumed.

Captions, dubs and chapters are added to the Mux video itself. To see chapters in the player, read this FAQ.

Description and tags

  • When a summarize job completes, its description and tags fill the video's description and tags, but only if they're empty. Anything already there stays, and the title never changes on its own.
  • Both fields are shown and editable in the video's Details tab.
  • Apply summary in the Details tab writes the newest summary's title, description or tags into the form, replacing what's there. Review them, then save. The button is disabled until the video has a completed summarize job.

Apply a summary to your entries

Entries of content types with a relation to Mux Asset show a Mux Robots panel in the Content Manager.

  1. Click Apply summary in the panel.
  2. Pick one of the entry's fields for each output (title, description, tags) and apply: the linked video's newest summary goes there as unsaved changes.
  3. Save the entry.

Only top-level relations are found, not ones inside components or dynamic zones. The panel reads the saved entry, so a video you link in the form shows after you save.

Directives on upload

  1. Create directives in the Mux Dashboard.
  2. List their ids in defaultDirectiveIds (see the plugin config).
  3. Every new upload gets them. The upload form lists them, checked, in its Automation section. Only people with Run Robots can uncheck one. An id Mux doesn't have is flagged there and isn't attached.

Good to know

  • How results arrive. Mux sends every job and directive run update by webhook, and Strapi stores it. Opening the Robots tab reads Mux once, to pick up jobs started outside Strapi. After that, the tab re-reads Strapi every 4 seconds while something runs, and stops when nothing does or the details close. Nothing polls Mux.
  • Who can read results. The newest summary and moderation scores are kept in the Mux Asset's robots_outputs. The plugin's content API returns description, tags and robots_outputs, so any role with find or findOne can read them, moderation scores included.
  • No automatic retries. The plugin never repeats a request to Mux on its own, so a run is never started or billed twice by accident. If Mux doesn't answer, the run shows as "Not confirmed" until it appears or you tell the plugin nothing is running.
  • One Strapi process. The check that holds back a second run while Mux hasn't confirmed the first works within one Strapi process. With several instances behind a load balancer, two clicks that reach different instances can each start, and bill, a run.

🧑‍⚖️ Permissions

Set the plugin's permissions per role in Settings → Administration Panel → Roles → Plugins → Mux Video Uploader. Super Admin has all of them. The plugin's admin API enforces them as well as the admin panel—

  • Read: see the plugin, its videos and their Robots results
  • Create: upload videos
  • Update: edit titles, descriptions, tags and captions, apply a summary to a video, and create a playback ID for a video that has none
  • Delete: delete videos
  • Run Robots: run workflows and directives, cancel jobs, and uncheck default directives when uploading. Running Robots consumes Mux AI units, so no role but Super Admin has it until you grant it.

Apply summary in the Content Manager follows the Content Manager's own permissions: anyone who can edit the entry can apply a summary to it.

Please note: End Users can only have read operation permissions (find, findOne and count). Write operations are not supported due to potential security reasons.

These endpoints follow Strapi's content API rules: filters, sort, fields and populate can't use private fields or the admin users behind createdBy/updatedBy (the request gets a 400), and find returns at most api.rest.maxLimit items (100 when unset), paginated with start/limit or pagination[page]/pagination[pageSize].

⬆️ Upgrading to 3.2

Your videos and captions are kept: Strapi only adds tables, columns and indexes (see Data stored by the plugin), and nothing is deleted or rewritten. There's no migration to run.

  1. Update the package, then rebuild the admin—

    npm install strapi-plugin-mux-video-uploader@^3.2.0
    npm run build
  2. Check that webhookSigningSecret matches the signing secret of your webhook in the Mux Dashboard. Webhooks are now verified: with a missing or wrong secret they get a 401 and new uploads stay in "Preparing".

  3. If your app's config/middlewares sets includeUnparsed: false on strapi::body, remove that option. Webhooks fail with 500 without it (see Webhooks).

  4. Start Strapi. It creates the muxrobotjobs table and the new muxassets columns and indexes on its own.

  5. If webhooks were rejected while the secret was wrong, resend them from Mux, for example with mux webhooks events replay.

  6. To use Mux Robots, follow Set up Robots.

  7. To see chapters in the player, make your app use one version of Mux Player (see Chapters don't show in the player).

What behaves differently—

  • The admin API enforces the plugin's permissions. The admin panel already applied Read, Create, Update and Delete; calls made directly to the plugin's admin API by roles without them now get a 403.
  • Failed requests to Mux aren't retried. A retried create could start a second billed Robots job, so every call to Mux is made once and its error is shown as is.
  • Saving a video's details only deletes caption tracks the form showed. Calls made directly to the update API delete a track only when its id is listed in known_track_ids.
  • Custom caption files have unguessable URLs. Mux downloads each custom caption file from a public URL that now names the stored file by its documentId instead of a sequential id, and gets the right file: before, every caption URL returned the first file stored.

💾 Data stored by the plugin

For reference: besides Strapi's own columns (id, document_id, created_at, updated_at, published_at, created_by_id, updated_by_id), the plugin keeps three tables.

muxassets: Mux Asset (plugin::mux-video-uploader.mux-asset), one row per video. Indexed on asset_id and upload_id (new in 3.2).

ColumnTypeHolds
titlestringThe video's title in Strapi
upload_id, asset_id, playback_idstringMux ids
signedbooleanSigned playback
is_readybooleanMux finished processing the video
error_messagestringMux's error for a failed upload or asset
duration, aspect_ratiodecimal, stringFrom Mux
asset_datajsonThe Mux asset as last read: tracks, playback ids, renditions
descriptiontextNew in 3.2. Filled by a summarize job when empty; editable
tagsjsonNew in 3.2. A list of strings, filled by a summarize job when empty; editable
robots_outputsjsonNew in 3.2. The newest summarize (job_id, completed_at, title, description, tags) and moderate (job_id, completed_at, exceeds_threshold, max_scores) results

muxrobotjobs: Mux Robot Job (plugin::mux-video-uploader.mux-robot-job), new in 3.2, hidden from the Content Manager. Indexed on mux_id and asset_id.

  • One row per Robots job or directive run Mux reports through webhooks, including runs on videos this Strapi doesn't have, so their results show if the video is added later.
  • A short-lived row for each run started from Strapi, until Mux confirms it.
  • Rows are never deleted, so results outlive Mux's own retention.
ColumnTypeHolds
mux_idstringrjob_… or drvrun_…; empty until Mux confirms a run started from Strapi
is_directive_runbooleanA directive run rather than a job
workflowstringThe workflow, e.g. summarize (jobs)
directive_id, directive_run_idstringThe directive and run that started a job, or the run's directive
statusstringpending, processing, completed, errored, cancelled; runs also partial; requested or unconfirmed while Mux hasn't confirmed a run
asset_idstringThe Mux asset id, matching a Mux Asset row's asset_id when this Strapi has the video
parameters, outputs, errorsjsonAs Mux reported them
units_consumedintegerMux AI units
run_datajsonA directive run's steps and timestamps
mux_created_at, mux_updated_atdatetimeMux's timestamps
detail_loadedbooleanOutputs and units are stored
request_id, requested_at, requested_bystring, datetime, integerWho started a run from Strapi, and when

muxtexttracks: Mux Text Track (plugin::mux-video-uploader.mux-text-track), unchanged: custom caption files (name, language_code, closed_captions, file) kept so Mux can download them from /mux-video-uploader/mux-text-tracks/:documentId.

🤝 Contributing

Contributions, issues and feature requests are welcome!

Developers for this plugin should take a look at the README_INTERNAL.md document for details on setting up dev environments.

If you encounter an error or have questions, please feel free to file inquiries on the Issues page for strapi-plugin-mux-video-uploader.

🗣 FAQ

My Strapi instance is not publicly available, can I still use Webhooks?

Yes! However, in order to make it work, you'll need a "Webhook Relay" that runs from within your network. You can use a Webhook Relay service like Smee (https://smee.io/) or ngrok (https://ngrok.com/) to forward Webhook events to an instance of Strapi behind a firewall.

I've installed the plugin, but the Strapi Admin UI doesn't show it

This happens when you need to rebuild your Strapi instance. To do this, you need delete the .cache, .strapi, and build folders (while Strapi is off) and restart to rebuild the instance.

Here is an example of how to do this on a unix-based operating system from within the Strapi application root—

rm -rf ./.cache ./build ./.strapi

Chapters don't show in the player

Chapters from Generate chapters need Mux Player 3.5 or later, but the admin runs 3.1.0: Strapi's own Media Library (@strapi/upload) ships that version, loads first, and takes the <mux-player> element for the whole admin, plugin included. Make your app install a single version—

  1. Add this to your app's package.json—

    // npm
    "overrides": { "@mux/mux-player-react": "^3.14.0" }
    
    // yarn
    "resolutions": { "@mux/mux-player-react": "^3.14.0" }
    
    // pnpm
    "pnpm": { "overrides": { "@mux/mux-player-react": "^3.14.0" } }
  2. Reinstall dependencies.

  3. Delete node_modules/.strapi/vite.

  4. Rebuild the admin (npm run build).

The Media Library's preview keeps working with the newer player.

Thumbnails don't show when hovering the player's timeline

The player loads those thumbnails from a storyboard VTT on image.mux.com. Strapi's default Content Security Policy only allows media from 'self' data: blob:, so the browser blocks it. Add https://image.mux.com to media-src (for the VTT) and img-src (for the images it points to) by replacing the 'strapi::security' entry of your config/middlewares file with this—

// Path: ./config/middlewares.js (in middlewares.ts, use `export default [`)
module.exports = [
  'strapi::logger',
  'strapi::errors',
  {
    name: 'strapi::security',
    config: {
      contentSecurityPolicy: {
        useDefaults: true,
        directives: {
          'connect-src': ["'self'", 'https:'],
          'img-src': ["'self'", 'data:', 'blob:', 'https://market-assets.strapi.io', 'https://image.mux.com'],
          'media-src': ["'self'", 'data:', 'blob:', 'https://image.mux.com'],
          upgradeInsecureRequests: null,
        },
      },
    },
  },
  // ...the rest of your middlewares, unchanged
];

Keep all of Strapi's defaults in the directives you change, as above: Strapi merges your list over its own entry by entry, so leaving them out drops some of them. Then restart Strapi.

Custom subtitles and captions aren't working

When uploading a video with custom text tracks, Mux asks for an URL pointing to these files. This feature currently works only on deployed Strapi installations.

When developing locally with Strapi, we don't have a globally reachable URL. Unlike webhooks with which we can use a local webhook proxy (e.g. Smee.io or ngrok), the plugin currently offers no way to configure a base URL to receive Mux's caption download requests. You'll still be able to upload videos, but the tracks won't be properly parsed by Mux.

Another approach would be to use tunnel service such as Cloudflare ZeroTrust or Tailscale to route requests against a publicly accessible hostname to your local instance. If you go this route, you will need to ensure that you configure Strapi to use the hostname when generating the Mux's caption download requests. Here is an example of how to do that—

Typescript - Server Config

// Path: ./config/server.ts
export default ({ env }) => ({
  // ... Existing properties set by Strapi
  url: env('PUBLIC_URL', 'https://your_host.domain.tld'),
});

Javascript - Server Config

// Path: ./config/server.js
module.exports = ({ env }) => ({
  // ... Existing properties set by Strapi
  url: env('PUBLIC_URL', 'https://your_host.domain.tld'),
});

❤️ Thanks

Strapi.io

📚 References

Submit your content

Share your work with the community and get it listed in the Strapi ecosystem for everyone to discover and use.

Submit
Submit your content