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-fields
composer require goodappr/moonshine-media-fields
composer require goodappr/moonshine-media-fields
composer require goodappr/moonshine-media-fields
composer require goodappr/moonshine-media-fields
The package auto-registers MediaFieldsServiceProvider.
Configuration
Publish config (optional)
php artisan vendor:publish --tag=moonshine-media-fields-config
php artisan vendor:publish --tag=moonshine-media-fields-config
php artisan vendor:publish --tag=moonshine-media-fields-config
php artisan vendor:publish --tag=moonshine-media-fields-config
php artisan vendor:publish --tag=moonshine-media-fields-config
Creates config/media_fields.php:
<?php
return [
'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',
],
];
<?php
return [
'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',
],
];
<?php
return [
'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',
],
];
<?php
return [
'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',
],
];
<?php
return [
'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();
});
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();
});
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 form
downloadOnIndex(true) — download icon
downloadPreviewModal(true) — download button in modal
showFileName(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);
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);
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 count
maxFileSizeKb(int) — max size per file
showPreview(true) — player for each
maxHeightContainer(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');
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');
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 URL
posterField(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');
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');
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');
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');
Video::make('Video', 'video')
->dir('uploads/video')
->posterColumn('video_poster')
->posterDir('uploads/posters');

VideoList — multiple video
Form:
- Video carousel or grid
maxFiles, maxFileSizeKb
gridColumns(2|3|4|null) — 2–4 columns or null for carousel
posterField(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')
);
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')
);
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');
VideoListPosters::make('Video posters', 'video_list_posters')
->videosColumn('videos');
VideoListPosters::make('Video posters', 'video_list_posters')
->videosColumn('videos');
VideoListPosters::make('Video posters', 'video_list_posters')
->videosColumn('videos');
Example Resource
<?php
namespace 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')
),
];
}
}
<?php
namespace 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')
),
];
}
}
<?php
namespace 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')
),
];
}
}
<?php
namespace 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')
),
];
}
}
<?php
namespace 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 —
.htaccess or config:
AddType audio/mpeg .mp3
AddType video/mp4 .mp4
AddType audio/mpeg .mp3
AddType video/mp4 .mp4
AddType audio/mpeg .mp3
AddType video/mp4 .mp4
AddType audio/mpeg .mp3
AddType video/mp4 .mp4
AddType audio/mpeg .mp3
AddType video/mp4 .mp4
-
ModSecurity — disable or adjust rules for media files.
-
Nginx — ensure types includes required MIME types.
Clear cache after update
php artisan config:clear
php artisan view:clear
php artisan config:clear
php artisan view:clear
php artisan config:clear
php artisan view:clear
php artisan config:clear
php artisan view:clear
php artisan config:clear
php artisan view:clear
License
MIT. See LICENSE.