MoonShine Media Fields
Audio, AudioList, Video and VideoList fields for MoonShine 4.x with preview, playback modals and custom audio player.

Table of Contents
Requirements
- PHP 8.2+
- Laravel 11+
- MoonShine 4.x
- Alpine.js (included with MoonShine)
Installation
composer require goodappr/moonshine-media-fieldscomposer require goodappr/moonshine-media-fields
The package auto-registers MediaFieldsServiceProvider.
Configuration
Publish config (optional)
php artisan vendor:publish --tag=moonshine-media-fields-configphp artisan vendor:publish --tag=moonshine-media-fields-config
Creates config/media_fields.php:
<?phpreturn ['audio' => ['extensions' => ['mp3', 'wav', 'ogg', 'm4a', 'aac', 'flac', 'webm'],'max_size_kb' => 20480,'dir' => 'audio','default_preload' => 'metadata',],'video' => ['extensions' => ['mp4', 'mov', 'avi', 'mkv', 'webm', 'flv', 'wmv'],'max_size_kb' => 102400,'dir' => 'video','default_preload' => 'metadata',],];<?phpreturn ['audio' => ['extensions' => ['mp3', 'wav', 'ogg', 'm4a', 'aac', 'flac', 'webm'],'max_size_kb' => 20480,'dir' => 'audio','default_preload' => 'metadata',],'video' => ['extensions' => ['mp4', 'mov', 'avi', 'mkv', 'webm', 'flv', 'wmv'],'max_size_kb' => 102400,'dir' => 'video','default_preload' => 'metadata',],];
Migration example
For storing audio and video in DB (string or json types):
Schema::table('posts', function (Blueprint $table) {$table->string('audio')->nullable();$table->json('audio_list')->nullable();$table->string('video')->nullable();$table->json('video_list')->nullable();$table->json('video_list_posters')->nullable();});Schema::table('posts', function (Blueprint $table) {$table->string('audio')->nullable();$table->json('audio_list')->nullable();$table->string('video')->nullable();$table->json('video_list')->nullable();$table->json('video_list_posters')->nullable();});
Usage
Audio — single audio
Form:
- File upload
showPreview(true)— player and preview (default)showPreview(false)— filename only
Index:
- Play button → modal with player
editOnIndex(true)— edit icon linking to formdownloadOnIndex(true)— download icondownloadPreviewModal(true)— download button in modalshowFileName(bool)— show filename
use MoonShine\MediaFields\Fields\Audio;Audio::make('Audio', 'audio')->dir('uploads/audio')->editOnIndex(true)->downloadOnIndex(true)->downloadPreviewModal(true)->showFileName(true)->preload('metadata')->loop(false);use MoonShine\MediaFields\Fields\Audio;Audio::make('Audio', 'audio')->dir('uploads/audio')->editOnIndex(true)->downloadOnIndex(true)->downloadPreviewModal(true)->showFileName(true)->preload('metadata')->loop(false);

AudioList — multiple audio
Form:
- Multiple file selection
maxFiles(int)— max countmaxFileSizeKb(int)— max size per fileshowPreview(true)— player for eachmaxHeightContainer(string)— container max-height (e.g.'400px')
Index:
- Play button + count → modal with list
downloadOnIndex(true)— download for each file
use MoonShine\MediaFields\Fields\AudioList;AudioList::make('Audio list', 'audio_list')->dir('uploads/audio')->maxFiles(10)->maxFileSizeKb(5120)->editOnIndex(true)->downloadOnIndex(true)->maxHeightContainer('400px');use MoonShine\MediaFields\Fields\AudioList;AudioList::make('Audio list', 'audio_list')->dir('uploads/audio')->maxFiles(10)->maxFileSizeKb(5120)->editOnIndex(true)->downloadOnIndex(true)->maxHeightContainer('400px');
Video — single video
Form:
- Upload, preview with video player
poster(string)— static poster URLposterField(Image $field)— poster as embedded Image field
Index:
- With poster: thumbnail + play button
- Without poster: camera icon
videoMaxHeight(string)— max-height for video in modal
use MoonShine\MediaFields\Fields\Video;Video::make('Video', 'video')->dir('uploads/video')->poster('/images/poster.jpg')->editOnIndex(true)->videoMaxHeight('400px');use MoonShine\MediaFields\Fields\Video;Video::make('Video', 'video')->dir('uploads/video')->poster('/images/poster.jpg')->editOnIndex(true)->videoMaxHeight('400px');
With poster via separate column:
Video::make('Video', 'video')->dir('uploads/video')->posterColumn('video_poster')->posterDir('uploads/posters');Video::make('Video', 'video')->dir('uploads/video')->posterColumn('video_poster')->posterDir('uploads/posters');

VideoList — multiple video
Form:
- Video carousel or grid
maxFiles,maxFileSizeKbgridColumns(2|3|4|null)— 2–4 columns ornullfor carouselposterField(VideoListPosters $field)— poster per video
Index:
- Camera icon + count or poster thumbnails
videoMaxHeight(string),gridColumns(int|null)
use MoonShine\MediaFields\Fields\VideoList;use MoonShine\MediaFields\Fields\VideoListPosters;VideoList::make('Videos', 'videos')->dir('uploads/videos')->maxFiles(5)->editOnIndex(true)->videoMaxHeight('320px')->gridColumns(2)->posterField(VideoListPosters::make('Posters', 'video_list_posters')->videosColumn('videos'));use MoonShine\MediaFields\Fields\VideoList;use MoonShine\MediaFields\Fields\VideoListPosters;VideoList::make('Videos', 'videos')->dir('uploads/videos')->maxFiles(5)->editOnIndex(true)->videoMaxHeight('320px')->gridColumns(2)->posterField(VideoListPosters::make('Posters', 'video_list_posters')->videosColumn('videos'));
VideoListPosters — posters for VideoList
Separate field for uploading posters for each video. Use with VideoList::posterField().
VideoListPosters::make('Video posters', 'video_list_posters')->videosColumn('videos');VideoListPosters::make('Video posters', 'video_list_posters')->videosColumn('videos');
Example Resource
<?phpnamespace App\MoonShine\Resources;use App\Models\Post;use MoonShine\MediaFields\Fields\Audio;use MoonShine\MediaFields\Fields\AudioList;use MoonShine\MediaFields\Fields\Video;use MoonShine\MediaFields\Fields\VideoList;use MoonShine\MediaFields\Fields\VideoListPosters;use MoonShine\Resources\ModelResource;class PostResource extends ModelResource{protected string $model = Post::class;public function fields(): array{return [Audio::make('Audio', 'audio')->dir('uploads/audio')->editOnIndex(true)->downloadOnIndex(true),AudioList::make('Audio list', 'audio_list')->dir('uploads/audio')->maxFiles(5),Video::make('Video', 'video')->dir('uploads/video')->editOnIndex(true)->videoMaxHeight('400px'),VideoList::make('Videos', 'videos')->dir('uploads/videos')->maxFiles(5)->gridColumns(2)->posterField(VideoListPosters::make('Posters', 'video_list_posters')->videosColumn('videos')),];}}<?phpnamespace App\MoonShine\Resources;use App\Models\Post;use MoonShine\MediaFields\Fields\Audio;use MoonShine\MediaFields\Fields\AudioList;use MoonShine\MediaFields\Fields\Video;use MoonShine\MediaFields\Fields\VideoList;use MoonShine\MediaFields\Fields\VideoListPosters;use MoonShine\Resources\ModelResource;class PostResource extends ModelResource{protected string $model = Post::class;public function fields(): array{return [Audio::make('Audio', 'audio')->dir('uploads/audio')->editOnIndex(true)->downloadOnIndex(true),AudioList::make('Audio list', 'audio_list')->dir('uploads/audio')->maxFiles(5),Video::make('Video', 'video')->dir('uploads/video')->editOnIndex(true)->videoMaxHeight('400px'),VideoList::make('Videos', 'videos')->dir('uploads/videos')->maxFiles(5)->gridColumns(2)->posterField(VideoListPosters::make('Posters', 'video_list_posters')->videosColumn('videos')),];}}
Themes and Styles
Uses MoonShine CSS variables:
--color-base,--color-base-text,--color-base-stroke--color-primary,--color-primary-text
Supports light and dark themes via .dark and [data-theme="dark"].
Troubleshooting
403 Forbidden when playing audio/video
Server may block MIME types. Check:
- Apache —
.htaccessor config:
AddType audio/mpeg .mp3AddType video/mp4 .mp4AddType audio/mpeg .mp3AddType video/mp4 .mp4
-
ModSecurity — disable or adjust rules for media files.
-
Nginx — ensure
typesincludes required MIME types.
Clear cache after update
php artisan config:clearphp artisan view:clearphp artisan config:clearphp artisan view:clear
License
MIT. See LICENSE.