PHPackages                             boccdotdev/polymedia - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Image &amp; Media](/categories/media)
4. /
5. boccdotdev/polymedia

ActiveCraft-plugin[Image &amp; Media](/categories/media)

boccdotdev/polymedia
====================

Universal media field for Craft CMS. Media URLs as native assets, with optional front-end rendering via Media Chrome.

v2.0.0(1w ago)3147↑22.2%proprietaryPHPPHP ^8.2CI passing

Since May 5Pushed 1w agoCompare

[ Source](https://github.com/boccdotdev/polymedia)[ Packagist](https://packagist.org/packages/boccdotdev/polymedia)[ RSS](/packages/boccdotdev-polymedia/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)Dependencies (11)Versions (11)Used By (0)

Polymedia for Craft CMS
=======================

[](#polymedia-for-craft-cms)

Universal media field for Craft CMS — HLS, YouTube, Vimeo, Spotify, Mux, TikTok, MP4, audio, and 10+ more providers as first-class assets, with [Media Chrome](https://www.media-chrome.org/) compatible player rendering.

What it's for
-------------

[](#what-its-for)

Polymedia stores external media URLs as lightweight `.pmedia` manifest files inside your existing Craft asset volumes. Each manifest is a real Asset element — searchable, relatable, permission-gated — backed by a database record holding type metadata, playback defaults, and related poster/caption/transcript assets.

The front-end renders `` + the correct provider web component via a single Twig call. No iframes, no embed codes, no JavaScript framework lock-in.

Requirements
------------

[](#requirements)

- Craft CMS 5.0+
- PHP 8.2+
- Media Chrome scripts on the front-end (loaded via the `scripts()` helper or your own bundler)

Installation
------------

[](#installation)

```
composer require boccdotdev/polymedia
php craft plugin/install polymedia
```

Field Setup
-----------

[](#field-setup)

Create a **Polymedia** field (appears in the field type picker). It extends the native Assets field, so it inherits all Craft's relation features — min/max limits, eager loading, element conditions.

Field settings:

- **Allowed Providers** — restrict which provider types can be selected (e.g. only YouTube + Mux). Leave empty for all.

> **Note:** The native Assets field still works with `.pmedia` files for headless or advanced use cases, but loses provider filtering.

Editions (Lite / Pro)
---------------------

[](#editions-lite--pro)

Polymedia ships with two Craft Plugin Store editions:

EditionIncludes**Lite** (free)URL media for all providers (including **paste** a Mux stream URL), field, player, posters, tracks**Pro**Everything in Lite **+** Mux Token settings, **Browse Mux library**, **Upload to Mux** (direct upload)Paste-a-Mux-URL playback never requires Pro. Library browse and direct upload do.

On non-public domains Craft allows unlicensed Pro for development (normal Craft trial rules).

Adding Media
------------

[](#adding-media)

Open the **Add media** menu and choose **From URL**, paste any supported URL, and give it a title. The plugin auto-detects the provider type and creates a `.pmedia` manifest asset. (With Pro + Mux credentials, the same menu also offers **Browse Mux library** and **Upload to Mux**.) The menu is available in two places:

- **Assets index** — sits beside **Upload files**. The manifest lands in the volume/folder you're currently browsing, exactly like an uploaded file.
- **Field selection modals** — when picking media for a Polymedia or Assets field. The manifest lands in the field's upload location.

No volume picker — the target follows your current location, falling back to the configured default volume (then the first volume you can write to) if none can be resolved.

For providers that can't be auto-detected (Shaka, Video.js, PeerTube), use the "Force Type" dropdown.

You can also set a poster image right on the **From URL** screen — image-only, with inline upload landing in the new item's folder. Posters and tracks can still be managed later on the asset edit screen.

### Mux library &amp; upload (Pro)

[](#mux-library--upload-pro)

1. Install **Pro** and open **Settings → Plugins → Polymedia → Mux**.
2. Enter a Mux API **Token ID** and **Token Secret** (env vars supported, e.g. `$MUX_TOKEN_ID`).
3. On the Assets index (or field asset modal), open the **Add media** menu and use:
    - **Browse Mux library** — live list from your Mux account; import creates a `.pmedia` or reuses one matched by **playback ID**.
    - **Upload to Mux** — browser direct upload (UpChunk); when Mux has a playback ID, Craft creates/reuses the `.pmedia`.

**Playback policy:** v1 imports **public** playback only. Signed-only assets are flagged in the browse UI and cannot be imported yet.

**Mux service fees** are separate from the Polymedia Pro license.

Optional setting **Delete Mux asset when Craft asset is deleted** (default **off**): when enabled, **hard-deleting** a Mux `.pmedia` also deletes the video in Mux. Soft-delete / trash never calls Mux.

**Notes on delete-from-Mux:** the remote delete runs **synchronously** in the hard-delete request (not a queue job). Deleting many Mux items at once may take longer. If the site is on **Lite** or credentials are missing while this setting is still on, Craft logs a **warning** and skips the Mux API call (the remote asset may remain until deleted in Mux).

### Per-item folders

[](#per-item-folders)

Each `.pmedia` is created inside its own folder (named after the title slug plus a short uid), keeping its poster and track files together and the parent volume tidy. Hard-deleting the item removes the folder and everything in it.

To reorganise items created before this behaviour existed, run:

```
./craft polymedia/migrate/folders --dry-run   # preview
./craft polymedia/migrate/folders             # apply

```

Items already in their own folder are skipped, so it's safe to re-run.

Front-End Setup
---------------

[](#front-end-setup)

### Scripts Helper

[](#scripts-helper)

Add to your layout template:

```
{{ craft.polymedia.scripts() }}
```

This emits `` tags for Media Chrome and the providers listed in your plugin settings.

#### CDN Mode (default)

[](#cdn-mode-default)

Zero-config. Scripts load from jsdelivr (or your configured CDN host).

```
{{ craft.polymedia.scripts({ providers: ['hls', 'youtube', 'vimeo', 'mux'] }) }}
```

#### Self-Host Mode

[](#self-host-mode)

1. Set **Script Loader Mode** to `self-host` in plugin settings.
2. Set **Self-Host Base URL** (e.g. `/dist/polymedia/`).
3. Copy the built provider files into that directory.

#### None Mode

[](#none-mode)

For Vite/webpack/import-map setups — `scripts()` emits nothing. Wire up the imports yourself.

Twig API
--------

[](#twig-api)

### `craft.polymedia.player(asset, options)`

[](#craftpolymediaplayerasset-options)

Renders a full `` player:

```
{{ craft.polymedia.player(media) }}
```

Options: `autoplay`, `loop`, `muted`, `playsinline`, `preload`, `crossorigin`, `poster`, `class`, `id`, `attrs`, `mediaAttrs`, `children`, `tracks`.

> **Note:** `controls` is intentionally **not** applied inside `player()`. Media Chrome supplies the control UI; native controls would conflict with ``.
>
> - `attrs` — HTML attributes on the outer ``
> - `mediaAttrs` — HTML attributes on the inner media element (e.g. `title`, `referrerpolicy`)

### `craft.polymedia.element(asset, options)`

[](#craftpolymediaelementasset-options)

Renders just the media element (no controller wrapper). Use this when you want a bare provider element without Media Chrome.

Options: `autoplay`, `loop`, `muted`, **`controls`**, `playsinline`, `preload`, `crossorigin`, `poster`, `mediaAttrs`. Defaults include `controls: true` so bare ``/`` elements are usable without a custom control bar.

### `craft.polymedia.data(asset)`

[](#craftpolymediadataasset)

Returns the parsed manifest data as an array.

### `craft.polymedia.is(asset)`

[](#craftpolymediaisasset)

Returns `true` if the value is a polymedia asset.

### `craft.polymedia.scripts(options)`

[](#craftpolymediascriptsoptions)

Renders script tags. Options: `providers` (array), `version` (Media Chrome major), `mode` (`cdn`/`self-host`/`none`).

### `craft.polymedia.poster(asset)`

[](#craftpolymediaposterasset)

Returns the resolved poster URL for a polymedia asset.

### `craft.polymedia.tracks(asset, role, siteId)`

[](#craftpolymediatracksasset-role-siteid)

Returns track-type related assets (captions, subtitles, descriptions).

### `craft.polymedia.transcript(asset)`

[](#craftpolymediatranscriptasset)

Returns the transcript related asset.

Asset Methods
-------------

[](#asset-methods)

Polymedia attaches a behavior to every `Asset` element so you can call media accessors directly on the asset — no `craft.polymedia.*` wrapper required. The methods mirror the Twig API one-to-one.

```
{% set media = entry.heroMedia.one() %}

{{ media.getPlayer() }}
{{ media.getElement() }}
{{ media.getPoster() }}
{% set data = media.getData() %}
{% for track in media.getTracks('captions') %}…{% endfor %}
{% set transcript = media.getTranscript() %}

{% if media.isPolymedia %}…{% endif %}
```

Twig getter shorthand also works — drop the `get` and the parens:

```
{{ media.player }}    {# = media.getPlayer() #}
{{ media.poster }}    {# = media.getPoster() #}
{{ media.data.title }}
```

### Available methods

[](#available-methods)

MethodReturnsEquivalent`getPlayer(options = {})``Markup``craft.polymedia.player(asset, options)``getElement(options = {})``Markup``craft.polymedia.element(asset, options)``getData()``array``craft.polymedia.data(asset)``getPoster()``string|null``craft.polymedia.poster(asset)``getTracks(role = 'captions', siteId = null)``Asset[]``craft.polymedia.tracks(asset, role, siteId)``getTranscript()``Asset|null``craft.polymedia.transcript(asset)``getIsPolymedia()``bool``craft.polymedia.is(asset)`The behavior is attached to all assets, but the methods safely return empty values for non-polymedia assets — guard with `media.isPolymedia` if you mix asset kinds in the same template.

### Choosing between styles

[](#choosing-between-styles)

Use the asset method style for terse, asset-centric templates:

```
{% for media in entry.gallery.all() %}

        {{ media.getPlayer({ controls: true }) }}
        {{ media.title }}

{% endfor %}
```

Use `craft.polymedia.*` when you're checking arbitrary values, in macros, or in shared partials where the input may not be an asset:

```
{% if craft.polymedia.is(value) %}
    {{ craft.polymedia.player(value) }}
{% endif %}
```

Examples by Provider
--------------------

[](#examples-by-provider)

### YouTube

[](#youtube)

```
{{ craft.polymedia.scripts({ providers: ['youtube'] }) }}
{{ craft.polymedia.player(media) }}
{# Renders:  #}
```

### Mux

[](#mux)

```
{{ craft.polymedia.scripts({ providers: ['mux'] }) }}
{{ craft.polymedia.player(media) }}
{# Renders:  #}
```

**Important:** Mux uses `playback-id`, not `src`.

### HLS

[](#hls)

```
{{ craft.polymedia.scripts({ providers: ['hls'] }) }}
{{ craft.polymedia.player(media) }}
{# Renders:  #}
```

### Spotify / Audio

[](#spotify--audio)

```
{{ craft.polymedia.scripts({ providers: ['spotify'] }) }}
{{ craft.polymedia.player(media) }}
{# Renders:  #}
```

Audio types use `` and emit a slotted `` for cover artwork.

### Native MP4/Audio

[](#native-mp4audio)

```
{{ craft.polymedia.player(media) }}
{# Renders:  #}
```

No additional provider scripts needed for native `` and ``.

Composing Controls
------------------

[](#composing-controls)

Use the `children` option to add Media Chrome control elements:

```
{{ craft.polymedia.player(media, {
    children: '

    '
}) }}
```

Posters and Cover Artwork
-------------------------

[](#posters-and-cover-artwork)

### Front-end player

[](#front-end-player)

Poster resolution order (highest priority first):

1. Explicit `poster` option in Twig (`false` suppresses entirely)
2. Item-level poster (attached via asset edit screen)
3. Derived thumbnail from manifest (auto-generated for YouTube, Vimeo, Mux, Cloudflare, Wistia)

For audio types, the poster is emitted as `` inside `` — available to themes that render a "now playing" cover.

### Auto-fetch &amp; create-time priority

[](#auto-fetch--create-time-priority)

When creating media **without** a user-selected poster:

1. **User poster** (create screen or asset editor) always wins — never overwritten by auto-fetch.
2. Else if **Auto-Fetch Poster** is on (URL create), or always for **Mux library/upload** imports: download a still into the item’s dedicated folder and attach it as the poster.
3. **Mux** uses the Image API first frame: `https://image.mux.com/{playbackId}/thumbnail.jpg?time=0` (Mux’s default without `time` is mid-video).
4. If the Mux image is not ready yet, a queue job retries with backoff.
5. Last resort: remote thumbnail URL only in manifest metadata (CP may show it until a local poster exists).

CP asset index / picker thumbs for `.pmedia` files use the related poster when present, else the remote thumbnail URL.

**Folder covers:** Craft has no first-class folder-cover API. Posters are co-located in each item’s dedicated folder so folder contents show the image; the parent index still uses Craft’s folder icon for the folder row itself.

Accessibility
-------------

[](#accessibility)

Attach `.vtt` files to video items via the asset edit screen, in three roles: **Captions**, **Subtitles**, and **Descriptions**. Each role is a multi-select picker that can also upload straight into the item's folder. The pickers appear only on video items (audio items show the poster picker alone).

Tracks are site-scoped — attach different language files to different Craft sites. On save, each track's `srclang` and `label` are derived from the current site (primary language subtag and locale display name).

Tracks auto-emit as `` elements inside the player. `crossorigin="anonymous"` is set automatically when VTT files are on a different domain.

Security
--------

[](#security)

The plugin warns when saving a URL that appears to contain a signed token (e.g. Mux signed playback, S3 presigned URLs) into a publicly accessible volume. The manifest file is readable by anyone with volume access — signed tokens in public manifests may leak.

Disable the warning in plugin settings if your setup handles access control at the filesystem level.

Troubleshooting
---------------

[](#troubleshooting)

### YouTube playback error 153

[](#youtube-playback-error-153)

YouTube refuses embedded playback (error code 153) when the embed request reaches `youtube.com` with no `Referer` header. The `` element builds its `` inside a shadow root, so this can't be fixed from your template — it's governed by your site's **referrer policy**, not the plugin.

Browsers default to `strict-origin-when-cross-origin`, which works. Error 153 means something on your site has overridden it to a stricter value such as `no-referrer` or `same-origin`, stripping the referrer to YouTube.

Fix it at the document level. Either send the header:

```
Referrer-Policy: strict-origin-when-cross-origin

```

…or add a meta tag to your page ``:

```

```

If you intentionally enforce a strict global policy, scope the relaxation to pages that embed YouTube rather than site-wide.

Supported Providers
-------------------

[](#supported-providers)

ProviderElementAuto-detectHLS```.m3u8` URLsDASH```.mpd` URLsShaka``Manual onlyMux```stream.mux.com`YouTube```youtube.com`, `youtu.be`Vimeo```vimeo.com`Spotify```open.spotify.com`TikTok```tiktok.com`, `vm.tiktok.com`Wistia```*.wistia.com`JW Player```cdn.jwplayer.com`Twitch```twitch.tv`Cloudflare```videodelivery.net`, `cloudflarestream.com`PeerTube``Manual onlyVideo.js``Manual onlyMP4/WebM/MOV```.mp4`, `.webm`, `.mov`Audio```.mp3`, `.m4a`, `.ogg`, `.wav`, `.flac`GraphQL
-------

[](#graphql)

Assets expose a `polymedia` field on the GraphQL Asset interface — `null` for anything that isn't a `.pmedia` asset. It works through any query that returns assets (asset fields on entries, the root `assets` query), so headless front ends need no special queries:

```
{
  assets(kind: "polymedia") {
    title
    polymedia {
      type          # mux | youtube | vimeo | hls | …
      providerId    # Mux playback ID, YouTube video ID, …
      element       # mux-video, youtube-video, video, …
      url
      title
      duration      # seconds, if known
      width
      height
      poster        # attached poster asset URL, falling back to the remote thumbnail
      tracks(role: ["captions", "subtitles"], siteId: 1) {
        kind        # captions | subtitles | descriptions
        url
        srclang
        label
        isDefault
        siteId
      }
      transcriptUrl
      metadata      # raw metadata as a JSON-encoded string
    }
  }
}
```

- **Enable per schema.** The field is gated behind a **Polymedia → View polymedia data** schema component (GraphQL → Schemas in the control panel). It is off by default everywhere, including the public schema — the field doesn't exist in a schema until you enable it.
- `tracks` accepts optional `role` (defaults to all three kinds) and `siteId` (defaults to the site the asset was queried in) arguments.
- Resolution is batched: any number of media items in a query costs a fixed number of database queries, and the `.pmedia` manifest file is never read.
- GraphQL itself requires Craft Pro (a Craft constraint, not a plugin one).
- The `polymedia` handle is reserved on asset field layouts — a custom field with that handle on a volume's layout would collide with the interface field.

Roadmap
-------

[](#roadmap)

- GraphQL types and queries
- Client-side metadata writeback (`loadedmetadata` → duration/dimensions)
- Live streaming UI hints
- Console command for self-host script bundling

Credits
-------

[](#credits)

Built on [Media Chrome](https://www.media-chrome.org/) and the [media-elements](https://github.com/muxinc/media-elements) monorepo by Mux.

License
-------

[](#license)

[Craft License](https://craftcms.github.io/license/). Lite is free to install from the Plugin Store; Pro is a paid edition.

###  Health Score

47

—

FairBetter than 93% of packages

Maintenance98

Actively maintained with recent releases

Popularity18

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~8 days

Total

9

Last Release

13d ago

Major Versions

v1.3.0 → v2.0.02026-07-11

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1289356?v=4)[William Bocchinelli](/maintainers/willbocc)[@willbocc](https://github.com/willbocc)

---

Top Contributors

[![willbocc](https://avatars.githubusercontent.com/u/1289356?v=4)](https://github.com/willbocc "willbocc (24 commits)")

---

Tags

audiovideoyoutubevimeoCraftcraft-pluginspotifyhlsmuxcraft5media-chrome

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/boccdotdev-polymedia/health.svg)

```
[![Health](https://phpackages.com/badges/boccdotdev-polymedia/health.svg)](https://phpackages.com/packages/boccdotdev-polymedia)
```

###  Alternatives

[wrav/oembed

A simple plugin to extract media information from websites, like youtube videos, twitter statuses or blog articles.

36208.3k3](/packages/wrav-oembed)[nystudio107/craft-transcoder

Transcode video &amp; audio files to various formats, and provide video thumbnails

437.9k1](/packages/nystudio107-craft-transcoder)[verbb/image-resizer

Resize assets when they are uploaded.

126276.0k8](/packages/verbb-image-resizer)[verbb/formie

The most user-friendly forms plugin for Craft.

101393.6k74](/packages/verbb-formie)[spacecatninja/imager-x

Ninja powered image transforms.

29405.1k39](/packages/spacecatninja-imager-x)[jonnitto/plyr

Plyr.io for Neos.io

1236.7k2](/packages/jonnitto-plyr)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
