Version 2.6.0
October 21, 2026
Added
polymedia/migrate/prune-folderslists the empty legacy folders thatpolymedia/migrate/sidecarsleaves behind, and deletes them with--apply. A legacy folder is named after a.pmediabeside it plus 8 letters or digits, or sits inside one with a name that ends the same way. It's deleted only when it holds no assets, trashed or not, no other folders and no files on disk that Craft never indexed. Nested folders go deepest first, and every folder that stays is listed with the reason. (#23)
Changed
- Replacing or removing a poster on the asset edit screen now deletes the old one when it's a file in the item's sidecar folder that nothing else uses, such as a fetched poster or an inline upload. Library images stay where they are. (#27)
Version 2.5.1
October 21, 2026
Fixed
- Bunny posters now follow thumbnail changes made in Bunny. Imports, webhooks and
polymedia/bunny/syncnotice a new thumbnail file, andpolymedia/bunny/syncalso checks the thumbnail's ETag or Last-Modified, so it notices a thumbnail replaced at the same URL too. Polymedia then replaces the poster it fetched, and deletes the old file when nothing else uses it. A poster an editor chose is never replaced. New posters record where they came from in the item'sposterSourcemetadata; posters fetched before 2.5.1 aren't tracked, so remove one from its asset and sync to fetch it again. (#25) - Control panel thumbnails of
.pmediaassets without a poster now use the thumbnail URL stored on the item, and derive one from the provider only when none is stored. Mux items imported before 2.5.0, which stored the first frame, still get Mux's own thumbnail. (#26)
Version 2.5.0
October 21, 2026
Added
- Bunny Stream hosting for Pro: library browsing and search, import, signed resumable direct uploads (tus), status, duration, dimension, MP4 and poster syncing, and
polymedia/bunny/sync. Imported videos keep the library ID, video ID and native*.b-cdn.nethost they were imported with. (#10, #12) - A Video Hosting Provider setting (Mux or Bunny Stream) for new imports and uploads. Authors get provider-neutral Browse video library and Upload video actions. Existing assets keep their own provider. (#8, #11)
- A per-provider Default Playback Format (HLS or MP4 when available) and a
playbackFormatoption onplayer()andelement(), rendered by both player engines. MP4 picks the highest ready rendition and falls back to HLS. New Mux uploads request a static rendition only when MP4 is preferred, and Mux rendition webhooks refresh renditions. (#9) - Optional signed Bunny Stream webhooks at
actions/polymedia/webhooks/bunny, enabled by a Bunny Webhook Verification Key environment reference. (#13) - Optional deletion of the Bunny video when its
.pmediais permanently deleted, off by default. Trashing never deletes the video. (#14) - Add video or audio → From existing asset (Lite) references a public MP4, WebM or MOV asset by UID without copying or owning it. Twig and GraphQL resolve the source's current URL. (#15)
- Pro, opt-in Automatically host uploaded MP4 videos for selected volumes: new MP4s from Craft's standard uploader go to the hosting provider and land as
.pmediaassets. Mixed batches keep Craft's queue behavior; Craft Cloud volumes can't be routed because Cloud has its own uploader, and if another routed volume's filesystem type has a custom uploader, native uploads are disabled for every volume of that type rather than storing raw MP4s. (#16) scripts()accepts a plain provider list, such asscripts(['mux', 'bunny']).- An Add video or audio menu on Polymedia fields and on Assets fields that explicitly allow the Polymedia file kind, between Add an asset and Upload a file. It offers the items the field's allowed providers accept, puts the new or reused
.pmediain the field's upload location and selects it without opening the asset picker, in Matrix, nested entries and slideouts too. On a new entry whose upload location can't be resolved yet, the.pmediagoes to your temporary uploads and moves into place on save, like Craft's own uploads. It hides while the field is full, on Assets fields with Allow uploads off, and when you can't save assets to the field's upload location. (#22)
Changed
- Add media is now Add video or audio on the Assets index and in asset pickers. A field's picker offers only the items its field allows, and From URL for a field only offers and saves the field's allowed providers.
- Asset selector modals with no field to select into, such as the one From existing asset opens, no longer show the menu. Uploading a video from there failed with "The field cannot accept another media item".
- New
.pmediaassets record the user who created them as their uploader, as uploaded files do. - A field restricted to one location with subfolders allowed now accepts media anywhere under that location, not only in its default upload subfolder.
- Mux posters and library thumbnails use Mux's own thumbnail, the asset's chosen thumbnail time or else the middle of the video, so they match the Mux dashboard instead of showing the first frame, which is often blank. Posters already fetched don't change.
Mux::firstFrameThumbnailUrl()is deprecated in favour ofMux::thumbnailUrl(). - Control panel thumbnails of
.pmediaassets keep their poster's aspect ratio instead of a square crop. - Imported and uploaded videos show their media URL and type as read-only technical details on the asset edit screen.
- Poster fetches no longer follow HTTP redirects.
- An explicit manifest destination that's missing or not writable is refused instead of being redirected to another volume.
- Mux upload status and completion now require the signed upload context from preflight, and the
.pmedialands in the folder authorized at the start of the upload. - For Mux, Bunny and MP4 items,
mediaAttrscan no longer overridesrc,playback-idortype.
Upgrade notes
- Nothing changes until you opt in: Mux stays the provider, HLS stays the playback format, and automatic routing stays off.
- No database migration, MP4 backfill, re-encode or remote deletion runs on upgrade.
- Bunny support covers anonymous playback from native
*.b-cdn.nethosts only. Bunny's hosted player, token authentication, referrer restrictions, DRM and custom CDN domains aren't supported.
Version 2.4.0
October 21, 2026
Added
- Optional Video.js v10 player engine. A new Player Engine setting (env-var capable, so one environment can try it first) switches
player(),element()andscripts()site-wide from Media Chrome to Video.js. Players use a packaged skin (Video.js Skin setting or askinoption), reserve their box with the stored aspect ratio (aspectRatiooption to override), translate to the site language, and accept your own controls throughchildren.craft.polymedia.playerEnginereports the active engine. Examples inexamples/videojs/show a brand theme and a Media Chrome look-alike that keeps a site's Media Chrome styling. Scripts load from@videojs/cdnpinned to the Video.js Version setting. Media Chrome stays the default, and its markup is unchanged. (#3)
Version 2.3.0
October 17, 2026
Added
- Mux items now store the aspect ratio Mux reports (
metadata.aspectRatio, e.g.16:9) and the video track's dimensions from import, upload,sync-status, and webhooks. Newcraft.polymedia.aspectRatio(asset)/asset.getAspectRatio()return it as a CSSaspect-ratiovalue (16 / 9) so templates can reserve the player's box before the video loads. Runcraft polymedia/mux/sync-statusto fill it in for existing items. (#5)
Fixed
scripts()loaded PeerTube frompeertube-video-element@0, which was never published. It now loads@1.- The GraphQL
widthandheightfields always returnednullbecause nothing stored them. Mux items now fill them. - A forced poster re-fetch (
PosterFetcher::fetchForItem(..., force: true)) no longer fails with “A file with the name … already exists”. Poster filenames now include a hash of the image, so the replacement saves beside the old poster and gets a new URL that CDNs haven't cached. The replaced poster is deleted once the new one is attached, but only when it is a Polymedia-managed file in the item's sidecar folder that nothing else uses. (#4)
Version 2.2.0
September 24, 2026
Existing installations need a dedicated sidecar volume before Polymedia can create new managed posters or accept inline poster and WebVTT uploads. Run
php craft polymedia/setup/sidecar-volume, or select a dedicated volume in Polymedia settings. Its storage path or bucket prefix must not overlap another volume. Existing media and attachments continue to work.See the sidecar setup and migration guide.
Mux token settings now require environment-variable references. If your settings contain literal credentials, move those values into environment variables and use references such as
$MUX_TOKEN_IDand$MUX_TOKEN_SECRET. Literal credentials will be rejected when saving plugin settings.
Added
- A dedicated sidecar volume for posters and WebVTT files created by Polymedia. New installations create a public local volume automatically. S3, Google Cloud Storage and other Craft filesystems are also supported.
polymedia/setup/sidecar-volumecreates the default local sidecar volume on existing installations.polymedia/migrate/sidecarsflattens legacy manifest folders and can relocate unshared managed files between sidecar volumes. It supports--dry-run, leaves uncertain or shared attachments in place, and reports failed moves.- Mux library search by title, asset ID, playback ID and passthrough text. Search covers the 1,000 most recent videos and shows a notice when the library exceeds that limit.
- Optional signed Mux webhooks for Pro. Ready, errored, updated and deleted events update existing media items. Invalid or stale signatures are rejected; unknown assets are ignored.
polymedia/mux/sync-statusrefreshes imported Mux items without a webhook endpoint. Use--mux-asset-idto target one video.- Local webhook setup instructions for the Mux CLI and public tunnels.
Changed
- New
.pmediamanifests stay directly in the editor's chosen folder. Managed posters and tracks use stable asset-UID folders in the sidecar volume, which is hidden from normal asset browsing. - Mux imports, uploads, webhooks and console sync share the same locked state-update path. Re-importing an item also refreshes its status and duration.
- Mux token and webhook settings require environment-variable references rather than literal credentials.
- Retired
polymedia/migrate/folders, which created the previous storage layout.
Fixed
- Hard-deleting a moved manifest no longer deletes its containing library folder. Cleanup targets only its owned sidecar folder.
- Adding media from a field picker now selects it into the field, including eligible assets outside the current page or source.
- Picker selection respects disabled assets, field limits and selection criteria. Failed replacement rendering leaves the old value intact.
- Background poster fetching preserves intervening editor choices and avoids duplicate generated posters.
- Webhooks return HTTP 503 when a lock or state write fails, allowing Mux to retry. Console sync reports those failures instead of counting them as successful.
- Manifest creation excludes the sidecar volume and handles duplicate titles without filename collisions.
- Migration preserves earlier successful results when a later operation fails. Retrying skips completed moves.
- Settings saves and automatic sidecar setup preserve environment-variable references.
Upgrade notes
- Preview legacy migration with
php craft polymedia/migrate/sidecars --dry-run. Legacy posters and tracks remain linked in their original locations because older versions did not record ownership. Do not remove folders that still contain retained files. - Webhooks remain optional. Upload polling and console sync still work without them.
Version 2.1.3
July 17, 2026
Fixed
- Browse Mux library pagination. The Next/Previous controls passed
page/limitasdataon a GET request, which becomes an ignored request body — so every page returned the first assets over again. They are now sent as queryparams.
Version 2.1.2
July 17, 2026
Fixed
- The Browse Mux library modal now scrolls when the grid is taller than the viewport. Garnish fades the modal in with an inline
display: blockthat overrode the shell'sdisplay: flex, so the body never filled the shell and its overflow was clipped by the fixed-height container. The body height is now set explicitly between the header and footer, so it scrolls and the footer stays reachable.
Version 2.1.1
July 17, 2026
Fixed
- Browse Mux library and Upload to Mux modals now scroll their content reliably. Craft's
.modal .body { overflow: hidden }could win an equal-specificity cascade tie depending on CSS load order and clip the grid; the plugin rules now outrank it.
Version 2.1.0
July 13, 2026
Added
- GraphQL support. Assets expose a
polymediafield on the Asset GraphQL interface (nullfor non-.pmediaassets) withtype,providerId,element,url,title,duration,width,height,poster,tracks(role, siteId),transcriptUrl, andmetadata. - “View polymedia data” schema component gates the field per schema (off by default, including the public schema).
MediaItems::getByAssetIds()andRelatedAssets::getForItemIds()batch lookups; GraphQL resolution costs a fixed number of queries regardless of item count.
Notes
- The
polymediahandle is now reserved on asset field layouts (a custom field with that handle would collide with the GraphQL interface field). - GraphQL itself requires Craft Pro (a Craft constraint, not a plugin one).
Version 2.0.0
July 11, 2026
Added
- Lite / Pro editions. Lite remains free URL media (including Mux paste). Pro gates Mux credentials UI, library browse, and direct upload.
- Mux settings: Token ID, Token Secret (env-overridable), and optional “delete from Mux on Craft hard-delete” (default off).
Muxservice wrappingmuxinc/mux-php(isConfigured, list/get assets, direct upload, delete, first-frame thumbnail URL).- Browse Mux library CP modal (Pro + credentials): live Mux Assets API grid, “In Craft” badge, import/reuse by playback ID.
PosterFetcherdownloads provider stills into the item folder and attaches as poster; user-selected posters win.FetchMuxPosterqueue job retries Mux first-frame downloads while assets are still processing.- CP asset thumbs for
.pmediafiles use the related poster (or remote thumbnail fallback). MediaItems::getByTypeAndProviderId()/getByTypeAndProviderIds()for import reuse.- CP config flag
Craft.Polymedia.muxEnabled(Pro + credentials). - Upload to Mux CP modal: direct upload via UpChunk, progress bar, poll until playback ID, create/reuse
.pmedia, first-frame poster job. - Vendored
@mux/upchunkin the CP asset bundle. - Optional delete Mux asset on Craft hard-delete (default off); soft-delete never calls Mux. Remote delete is synchronous (v1); Lite/unconfigured skips log a warning rather than failing silently.
- Light Mux status badge + asset id on the asset editor for imported/uploaded items.
- README: Lite/Pro editions, Mux credentials, browse/upload, poster priority, folder-image limits, public playback.
Changed
- CP: the separate Add media URL, Browse Mux library, and Upload to Mux buttons are now a single Add media disclosure menu (From URL / Browse Mux library / Upload to Mux) beside Upload files, on both the Assets index and field selection modals.
- Mux upload modal: Craft-style file picker (hidden input + upload button + filename), a cancel notice, and an auto-sizing shell so progress/status rows never clip.
- Mux thumbnail URLs use first frame (
?time=0) instead of Mux’s mid-video default. autoFetchPosteris implemented and re-enabled in settings (URL create path downloads a poster when none is supplied).- Mux library imports always attempt a first-frame poster when the item has no poster.
- License changed from MIT to the Craft License (
proprietary) with the introduction of the commercial Pro edition. Lite remains free to install from the Plugin Store.
Version 1.3.0
July 11, 2026
Added
metadatacolumn onpolymedia_itemsso manifest extras (thumbnail URL, provider hints) live in the DB; enables DB-firstdata()/ManifestWriter::read()without a filesystem round-trip per render.- Lazy metadata backfill when reading older rows that only have the
.pmediafile populated. options.mediaAttrsonplayer()/element()to pass attributes through to the inner media element (title,referrerpolicy, etc.).attrscontinues to target<media-controller>only.EditorContentservice for CP editor fields and element-select configs (extracted fromPlugin).- Composer scripts:
check-cs,fix-cs,phpstan,test. - Easy Coding Standard (
ecs.php) and PHPStan (phpstan.neon, level 5) config. - GitHub Actions CI on PHP 8.2 and 8.3 (phpunit + ECS + PHPStan).
Changed
MediaItems::getByAssetId()is memoized per request; cache invalidated onsave()/deleteByAssetId().ManifestWriter::read()prefers the DB record; filesystem is fallback + backfill path.savePoster()/saveTracks()logic lives onRelatedAssets;Pluginkeeps thin public proxies for BC.Plugin.phpslimmed (~1,370 → ~1,070 lines) viaEditorContentextraction.- Schema version
1.1.0→1.2.0(runphp craft up/ plugin migrate).
Version 1.2.2
July 11, 2026
Fixed
- Soft-deleting a polymedia asset no longer drops its
MediaItemRecordor related poster/track attachments. Restore from trash keeps metadata intact; cleanup still runs on hard delete (including the dedicated item folder). craft.polymedia.element()now emits the nativecontrolsattribute when enabled (default).player()still omits it so Media Chrome controls are not doubled.- CDN host and self-host base URL settings with env vars (
$VAR/ aliases) are resolved viaApp::parseEnv()inscripts(). - VTT validation (
validateVttOnUpload) runs when attaching tracks from the asset editor; invalid files are skipped. - Double-encoded
alton audio cover<img>posters. - CP JS strings (
Add media URL,Media item created.) are registered for translation.
Changed
- Settings UI no longer shows unimplemented options: Attachments Volume, Auto-Fetch Poster, Require Captions for Video, Caption Language from Site, Restrict Asset Kinds. Model properties remain for project config BC (removal planned for 2.0).
- README clarifies that
controlsapplies toelement()/getElement(), notplayer().
Version 1.2.1
May 29, 2026
Fixed
craft upcould fail when a core or third-party migration re-saved sections before this plugin'sm260507_000000_field_settingsmigration had run. The element-validation hook queried the not-yet-createdpolymedia_field_settingstable and threwThe table does not exist.AssetFieldSettings::getAllowedProviders()now returns no restrictions when the table is absent, so migrations complete cleanly.
Version 1.2.0
May 29, 2026
Added
- Track pickers on the asset edit screen for video items — Captions, Subtitles, and Descriptions. Each is a multi-select
.vttpicker that can also upload into the item's folder. Tracks are site-scoped, withsrclangandlabelderived from the current site on save. Previously tracks could only be attached programmatically viaRelatedAssets::attach(). - Poster image picker on the Add media URL create screen, so a poster can be set when first creating an item — previously only available on the asset edit screen. The picker is image-only and supports inline upload, with uploads landing in the item's own folder.
- Add media URL button on the standalone Assets index toolbar, beside Upload files. Previously only available in field selection modals.
- Each new
.pmediais now created inside its own dedicated folder (named after the title slug plus a short uid), so its poster and track files sit alongside it instead of cluttering the parent folder. Posters and tracks uploaded inline are co-located into that folder automatically. polymedia/migrate/foldersconsole command to move existing.pmediaitems (and their co-located poster/track files) into per-item folders. Supports--dry-run; safe to run repeatedly — items already in their own folder are skipped.
Changed
- Removed the target-volume dropdown from the Add media URL screen. New
.pmediamanifests now land in the volume/folder the user is currently browsing (or the field's upload location), matching how Craft handles normal asset uploads. Falls back to the configured default volume, then the first writable volume. - Hard-deleting a
.pmedianow also removes its dedicated folder and the poster/track files inside it. A.pmediathat shares a folder with other items leaves the folder untouched.
Version 1.1.0
May 7, 2026
Added
- "Allowed Providers" setting on plain
craft\fields\Assetsfields when thepolymediakind is enabled. Mirrors the same filter onPolymediaField, so any Assets field can restrict media URL selections to specific providers. Toggles live as the kind is checked/unchecked. polymedia_field_settingstable for per-field plugin settings keyed by field UID.AssetFieldSettings,ProviderFilterservices.PolymediaAssetFieldBehaviorattached to native Assets fields.- Plugin icon (
src/icon.svg).
Changed
- Provider filter validation extracted to a shared service.
PolymediaFieldand plain Assets fields now run identical validation logic.
Removed
- Unused per-placement poster override code (
FieldOverridesservice,FieldRelationRecord,polymedia_field_relationstable). The schema, service, andallowPosterOverrideconfig key were never reachable from Twig or the CP UI.
Version 1.0.2
May 6, 2026
Added
PolymediaAssetBehaviorattached to allAssetelements, exposinggetPlayer(),getElement(),getData(),getPoster(),getTracks(),getTranscript(), andgetIsPolymedia()directly on assets in Twig.
Version 1.0.1
May 6, 2026
Fixed
- Restored "Add Media URL" button in asset selection modals and slideouts.
Version 1.0.0
May 4, 2026
Added
- Initial release.
- URL detection for 16 media providers (HLS, DASH, Mux, YouTube, Vimeo, Spotify, TikTok, Wistia, JW Player, Twitch, Cloudflare, Shaka, PeerTube, Video.js, MP4, Audio).
.pmediaJSON manifest files stored as Craft assets.MediaItemRecorddatabase mirror for fast index queries.- Deterministic thumbnail derivation (YouTube, Vimeo, Mux, Cloudflare, Wistia).
- Custom
polymediafile kind with asset index table attributes (Type, Provider, Duration). - CP "Add media URL" modal with signed-URL warning.
- Asset edit sidebar panel with playback defaults.
- Related assets system (poster, captions, subtitles, descriptions, transcript).
- VTT validation with BOM stripping.
PolymediaFieldfield type (extends Assets) with provider filtering and per-placement poster overrides.Rendererservice withplayer(),element(),data(),is(),scripts().craft.polymedia.*Twig variable API.- Media Chrome
<media-controller>rendering with correct provider element tags. - Script loader modes: CDN, self-host, none.
- Asset indexer reconciler for orphaned
.pmediafiles. - 102 unit tests, 268 assertions.