@codingfactory/mediables-vue
v2.4.16
Published
Vue components for Laravel Mediables package with comprehensive video support
Readme
Laravel Mediables v2.0
A powerful, backend-focused Laravel media management package with a clean, trait-based API. Inspired by Spatie's Media Library but built for maximum flexibility and performance.
Upgrading from v1.x? Check out our comprehensive Upgrade Guide for a smooth migration path.
What's New in v2.0
Service-Based API
use Mediables\Facades\MediaService;
// Option 1: Two-step (store then attach)
$media = MediaService::storeMediaFile(
$request->file('image'),
'public', // disk
'products', // collection
['alt' => 'Photo'] // custom properties
);
$product->attachMedia($media, 'images');
// Option 2: Combined method (recommended)
$media = MediaService::uploadAndAttachToModel(
auth()->user(), // actor
$request->file('image'),
$product, // target model
'images', // collection
['alt' => 'Photo'] // properties
);Key Features
- Trait-Based Architecture - Simple, clean integration with any Eloquent model
- Service-Based Upload - Flexible upload workflows via MediaService facade
- Media Collections - Organize media with powerful collection definitions
- Smart Image Processing - Automatic conversions, responsive images, and 46 PIXI.js filters
- Cloud-First Design - Native S3, CDN, and signed URL support
- Performance Optimized - Multi-layer caching and lazy loading
- Backward Compatible - Smooth migration path from v1.x
- Modular Frontend - Optional Vue components (Blade and Livewire coming soon)
Table of Contents
- Installation
- Quick Start
- Defining Media Collections
- Working with Media
- Image Processing
- Frontend Components
- Configuration
- Migration from v1.x
- API Reference
- Contributing
Installation
composer require mediables/mediables:^2.0Publish and run migrations:
php artisan vendor:publish --tag=mediables-migrations
php artisan vendor:publish --tag=mediables-config
php artisan migrateOptional Frontend Packages
# Vue 3 components (available now)
npm install @mediables/vue
# Blade components (coming soon)
# composer require mediables/blade-components
# Livewire components (coming soon)
# composer require mediables/livewire-componentsQuick Start
1. Add Traits to Your Model
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Mediables\Traits\InteractsWithMediables;
class Product extends Model
{
use InteractsWithMediables;
public function registerMediaCollections(): void
{
$this->addMediaCollection('images')
->acceptsMimeTypes(['image/*'])
->maxNumberOfFiles(10);
$this->addMediaCollection('documents')
->acceptsMimeTypes(['application/pdf'])
->singleFile();
}
}2. Upload and Attach Media
use Mediables\Facades\MediaService;
// Option 1: Two-step (store then attach)
$media = MediaService::storeMediaFile(
$request->file('image'),
'public', // disk
'products', // collection
['alt' => 'Photo'] // custom properties
);
$product->attachMedia($media, 'images');
// Option 2: Combined method (recommended)
$media = MediaService::uploadAndAttachToModel(
auth()->user(), // actor
$request->file('image'),
$product, // target model
'images', // collection
['alt' => 'Photo'] // properties
);3. Retrieve and Display Media
// Get all media in collection
$images = $product->getMedia('images');
// Get first media
$mainImage = $product->getFirstMedia('images');
// Get URL (original)
$originalUrl = $mainImage->getUrl();
// Get conversion URL (use Media model method)
$thumbUrl = $mainImage->getUrl('thumb');
// Get first media URL directly (NO conversion param)
$firstUrl = $product->getFirstMediaUrl('images');
// Check if has media
if ($product->hasMedia('images')) {
// ...
}
// Count media
$count = $product->getMedia('images')->count();Defining Media Collections
Define what media your models accept:
public function registerMediaCollections(): void
{
// Single avatar image
$this->addMediaCollection('avatar')
->singleFile()
->acceptsMimeTypes(['image/jpeg', 'image/png']);
// Product gallery with limits
$this->addMediaCollection('gallery')
->acceptsMimeTypes(['image/*'])
->maxFileSize(5 * 1024 * 1024) // 5MB per file
->maxNumberOfFiles(20);
// Documents with specific types
$this->addMediaCollection('documents')
->acceptsMimeTypes(['application/pdf', 'application/msword'])
->maxNumberOfFiles(10);
}Working with Media
Upload Workflow (Service)
use Mediables\Facades\MediaService;
// Option 1: Two-step (store then attach)
$media = MediaService::storeMediaFile(
$request->file('image'),
'public', // disk
'products', // collection
['alt' => 'Photo'] // custom properties
);
$product->attachMedia($media, 'images');
// Option 2: Combined method (recommended)
$media = MediaService::uploadAndAttachToModel(
auth()->user(), // actor
$request->file('image'),
$product, // target model
'images', // collection
['alt' => 'Photo'] // properties
);Managing Collections
// Clear all media from collection
$product->clearMediaCollection('images');
// Attach existing media
$product->attachMedia($existingMedia, 'images');
// Detach media
$product->detachMedia($media, 'images');Working with Conversions
use Mediables\Facades\MediaService;
// Get conversion URL
$thumbnailUrl = $media->getUrl('thumb');
// Get responsive images srcset
$srcset = $media->getSrcset();
// Generate conversions manually
MediaService::generateConversions($media);Configuration
Simplified configuration in config/mediables.php:
return [
// Storage
'disk' => env('MEDIA_DISK', 'public'),
'conversions_disk' => env('MEDIA_CONVERSIONS_DISK', null),
// Processing
'queue_connection' => env('MEDIA_QUEUE_CONNECTION', 'sync'),
'conversions' => [
'thumb' => ['width' => 150, 'height' => 150],
'preview' => ['width' => 500, 'height' => 500],
],
// Video Provider (currently only 'bunny' is supported)
'video' => [
'provider' => env('VIDEO_PROVIDER', 'bunny'),
'bunny' => [/* ... */], // Bunny.net config
],
// Validation
'max_file_size' => 10 * 1024 * 1024, // 10MB
'allowed_mimes' => 'jpeg,jpg,png,gif,webp,svg,pdf',
// Features
'generate_responsive_images' => true,
'versioning' => false,
'cdn_url' => env('MEDIA_CDN_URL'),
];Environment Variables for Video Providers
To configure your video provider, set VIDEO_PROVIDER in your .env file. Currently only bunny is supported.
Bunny.net Configuration:
When VIDEO_PROVIDER is set to bunny, the following environment variables are required:
BUNNY_VIDEO_LIBRARY_ID="your_bunny_video_library_id"
BUNNY_VIDEO_API_KEY="your_bunny_video_api_key"
BUNNY_VIDEO_HOSTNAME="video.bunnycdn.com" # Default is usually sufficient
BUNNY_VIDEO_CDN_HOSTNAME="your_library_id.b-cdn.net" # or play.bunnycdn.com
BUNNY_VIDEO_WEBHOOK_SECRET="your_webhook_secret" # Used for verifying webhooksImage Processing
Automatic Conversions
Define conversions that run automatically:
'conversions' => [
'thumb' => [
'width' => 150,
'height' => 150,
'fit' => 'crop',
'quality' => 80,
],
'preview' => [
'width' => 500,
'height' => 500,
'fit' => 'contain',
],
],On-the-fly Manipulation
use Mediables\Services\Core\ImageProcessor;
$processor = app(ImageProcessor::class);
// Generate specific conversion
$processor->generateConversion($media, 'thumb', [
'width' => 150,
'height' => 150,
'fit' => 'crop',
]);Available Filters
The package includes 46 image filters:
use Mediables\Services\MediaManipulationService;
$manipulationService = app(MediaManipulationService::class);
// Submit a manipulation request (validated data from ManipulateMediaRequest)
$manipulationService->manipulate([
'media_uuid' => $media->uuid,
'manipulations' => [
'filter' => 'ColorMatrix-1977',
'brightness' => 10,
'contrast' => 15,
],
]);
// Apply server-side filters to a media item
$processedUrl = $manipulationService->applyFilters($media, [
['name' => 'ColorMatrix-1977'],
]);Responsive Images
Responsive image generation is controlled by the generate_responsive_images config option. When enabled, responsive variants are generated automatically during upload.
// Enable in configuration
'responsive_images' => [
'enabled' => true,
'widths' => [320, 640, 1024, 1920, 2560],
],
// Get srcset for HTML
$srcset = $media->getSrcset('preview');
// Use in Blade templates
<img src="{{ $media->getUrl() }}"
srcset="{{ $media->getSrcset() }}"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw">Working with Variants
Media variants allow you to create processed versions of your media:
// Create a variant with specific processing
$variant = $originalMedia->createVariant([
'manipulation' => 'cropped',
'dimensions' => ['width' => 500, 'height' => 500],
'filters' => ['brightness' => 10, 'contrast' => 15]
]);
// Check processing status
if ($variant->isVariantCompleted()) {
$variantUrl = $variant->getUrl();
}
// Get all variants for media
$variants = $originalMedia->variants;
// Delete specific variant
$variant->delete();Signed URLs for Private Files
// Generate signed URL (expires in 60 minutes by default)
$signedUrl = $media->getSignedUrl();
// Custom expiration time
$signedUrl = $media->getSignedUrl(120); // 2 hours
// Check if URL is still valid
if ($media->isSignedUrlValid($signedUrl)) {
// URL is still valid
}Batch Operations
For bulk operations, iterate over your media collection using the standard service methods:
use Mediables\Facades\MediaService;
// Bulk upload with loop
$uploadResults = [];
foreach ($files as $file) {
$uploadResults[] = MediaService::storeMediaFile($file, 'public', 'products');
}
// Bulk delete with loop
foreach ($mediaItems as $media) {
MediaService::deleteMedia($media);
}
// Generate conversions for multiple media
foreach ($mediaCollection as $media) {
MediaService::generateConversions($media);
}Event System
The package dispatches comprehensive events:
// Listen to events
Event::listen(\Mediables\Events\MediaUploaded::class, function ($event) {
Log::info('Media uploaded: ' . $event->media->uuid);
});
// Available events:
// - MediaUploaded
// - MediaManipulated
// - MediaDeleted
// - MediaAttached
// - MediaDetached
// - ConversionGeneratedFrontend Components
Frontend components are now in separate, optional packages:
Vue 3 Components
npm install @mediables/vue<template>
<ModelMediaManager
:model-type="'product'"
:model-uuid="product.uuid"
collection="images"
:max-files="10"
@upload-complete="handleUpload"
/>
</template>
<script setup>
import { ModelMediaManager } from '@mediables/vue';
</script>Blade Components (Coming Soon)
# composer require mediables/blade-components<x-mediables-upload
:model="$product"
collection="images"
/>
<x-mediables-gallery
:model="$product"
collection="images"
/>Livewire Components (Coming Soon)
# composer require mediables/livewire-components<livewire:mediables-manager
:model="$product"
collection="images"
/>API Reference
The complete REST API is documented using OpenAPI 3.0.3:
- OpenAPI Spec: docs/openapi.yaml
- Interactive Docs: View in Redoc
- Swagger UI: Import
docs/openapi.yamlinto Swagger Editor
Key API Groups
- Media Management: Upload, list, update, delete media
- Image Editing: Apply filters, crop, save variants
- Video Processing: Encode, transcode, apply effects
- Albums: Organize media into hierarchical albums (requires
enable_albums=true) - Admin: Administrative endpoints for media management
Authentication
All API endpoints require authentication via Laravel Sanctum:
// Set authorization header
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;Media Upload
POST /api/v1/media/upload
Content-Type: multipart/form-data
Parameters:
- file: File (required)
- collection: string (optional)
- custom_properties: object (optional)
- owner_uuid: string (required)
- owner_type: string (required)Response:
{
"data": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"file_name": "image.jpg",
"mime_type": "image/jpeg",
"size": 1024000,
"collection_name": "gallery",
"original_url": "https://cdn.example.com/media/image.jpg",
"conversion_urls": {
"thumb": "https://cdn.example.com/media/conversions/image-thumb.jpg",
"preview": "https://cdn.example.com/media/conversions/image-preview.jpg"
},
"custom_properties": {},
"created_at": "2025-01-24T10:00:00Z"
}
}List Media
GET /api/v1/media?collection=gallery&search=product&page=1&per_page=20Apply Image Filters
POST /api/v1/media/manipulate
Content-Type: application/json
{
"image_data": "data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"original_media_uuid": "550e8400-e29b-41d4-a716-446655440000",
"crop_params": {
"x": 0,
"y": 0,
"width": 100,
"height": 100,
"scaleX": 1,
"scaleY": 1
},
"filter_params": [
{"name": "ColorMatrix-1977"}
],
"async": true
}Response (Sync, 201):
{
"data": {
"uuid": "770e8400-e29b-41d4-a716-446655440000",
"url": "https://cdn.example.com/media/manipulated.jpg"
}
}Response (Async, 202):
{
"id": "job-uuid-123",
"status": "queued",
"status_url": "https://api.example.com/api/v1/media/manipulations/job-uuid-123"
}Polling Status:
GET /api/v1/media/manipulations/{job_id}Legacy Aliases (Deprecated):
media_uuid(alias fororiginal_media_uuid)manipulations(stored as metadata only)- Usage triggers
X-Mediables-Deprecatedheader.
Generate Conversions
POST /api/v1/media/{media_uuid}/conversionsFor complete API documentation, see docs/api.md.
Image Processing
Available Filters
Adjustment Filters
- brightness (-100 to 100) - Adjust image brightness
- contrast (-100 to 100) - Adjust contrast levels
- saturation (0 to 300) - Control color saturation
- hue (0 to 360) - Rotate hue values
- grayscale (boolean) - Convert to black and white
Blur Effects
- KawaseBlur - High-quality blur effect
- ZoomBlur - Radial motion blur
- MotionBlur - Directional motion blur
- Bloom - Soft glow effect
Vintage & Artistic
- ColorMatrix-1977 - Instagram-style vintage filter
- Sepia - Classic sepia tone
- OldFilm - Vintage film with grain
- Dot - Halftone newspaper effect
- CRT - Retro monitor effect
Distortion Effects
- Twist - Spiral distortion
- Shockwave - Ripple effect
- BulgePinch - Lens distortion
Usage Examples
use Mediables\Services\Core\ImageProcessor;
$processor = app(ImageProcessor::class);
// Generate a conversion with custom config
$processor->generateConversion($media, 'custom', [
'width' => 500,
'height' => 500,
'fit' => 'contain',
'quality' => 80,
'manipulations' => [
'brightness' => ['level' => 20],
'contrast' => ['level' => 25],
],
]);Performance
Caching Strategy
The package implements multi-layer caching:
// URL caching (reduces database queries)
'enable_url_cache' => true,
'url_cache_duration' => 60, // minutes
// CDN integration
'cdn' => [
'default' => env('MEDIA_CDN_URL'),
'invalidate_on_change' => true,
],
// Lazy conversion generation
'lazy_conversions' => true,Queue Configuration
Optimize performance with granular queue configuration:
'queues' => [
'upload' => 'default', // File uploads
'conversions' => 'default', // Conversion generation
'conversions_high' => 'high', // Priority conversions
'conversions_low' => 'low', // Background conversions
'responsive' => 'default', // Responsive image generation
'delete' => 'low', // File deletion
],Database Optimization
- Uses UUIDs for efficient lookups
- Polymorphic relationships for flexibility
- Indexed foreign keys and commonly queried columns
- Optimized queries with eager loading
Monitoring
// Get performance metrics (debug mode only)
GET /api/v1/admin/media/performance
// Monitor conversion progress
GET /api/v1/media/{media}/conversions/progressArtisan Commands
# Generate missing conversions
php artisan mediables:generate-conversions
# Clean up orphaned media records
php artisan mediables:cleanup-orphans
# Verify media files exist in storage
php artisan mediables:verify-files
# Warm CDN cache
php artisan mediables:warm-cache
# Test media relationships
php artisan mediables:test-relationships
# Test S3 connectivity
php artisan mediables:test-storageScheduling Orphan Cleanup
Scheduling is the consuming application's responsibility. This command is designed to be safe for cron/schedulers by using chunked processing and an optional runtime bound (--limit).
php artisan mediables:cleanup-orphans --grace-period=7 --chunk=200 --limit=2000Testing
The package includes comprehensive testing support:
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Mediables\Facades\MediaService;
// Fake storage for testing
Storage::fake('public');
// Create fake upload
$file = UploadedFile::fake()->image('product.jpg', 1200, 800);
// Test media upload
$media = MediaService::storeMediaFile($file, 'public', 'products');
// Assert file exists
Storage::disk('public')->assertExists($media->full_path);
// Test media attachment
$product = Product::factory()->create();
$product->attachMedia($media, 'gallery');
$this->assertTrue($product->hasMedia('gallery'));
$this->assertEquals(1, $product->getMedia('gallery')->count());Migration from v1.x
Quick Migration
Run the migration command:
php artisan mediables:migrate-v2This will:
- Analyze your current setup
- Update trait usage automatically
- Migrate configuration
- Provide detailed migration report
Manual Migration
- Update Traits:
| Old (v1.x) | New (v2.0) |
|---|---|
| Mediables\Traits\HasMediaCollections | Mediables\Traits\InteractsWithMediables |
- Update Method Calls:
| Old Method | New Method |
|------------|------------|
| getMediaByCollection() | getMedia() |
| getFirstMediaByCollection() | getFirstMedia() |
| hasMediaCollection() | hasMedia() |
| detachMedia() | detachMedia() |
| syncMediaCollection() | clearMediaCollection() + attachMedia() loop |
For detailed migration instructions, see UPGRADE.md.
Migration from Spatie Media Library
Migrating from Spatie Media Library? We've got you covered:
# The package includes backward compatibility
# Your existing database schema works with minor adjustments
# See detailed migration guide: docs/migration-guide.mdKey differences:
- Uses UUIDs instead of auto-incrementing IDs
- Improved polymorphic relationship structure
- Enhanced querying capabilities
- Better performance optimizations
For a complete migration guide, see docs/migration-guide.md.
Database Schema
Core Tables
- media - Stores media file metadata and properties
- mediables - Polymorphic pivot table linking media to models
- media_variants - Tracks processing status of media variants
- media_albums - Optional album organization (if enabled)
- media_audits - Optional audit trail (if enabled)
Key Fields
Media Table:
uuid (primary key)
file_name
original_file_name
mime_type
size
disk
collection_name
custom_properties (JSON)
created_at, updated_atMediables Table:
media_uuid (foreign key)
mediable_uuid
mediable_type
collection_name
order_columnRequirements
- PHP 8.1 or higher
- Laravel 11.0 or higher
- Intervention Image 3.11 or higher
- Vue 3.4+ (for Vue components)
- Ionic 8.0+ (for mobile components)
License
The MIT License (MIT). Please see License File for more information.
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
Support & Documentation
- Documentation: Full Documentation
- API Reference: API Documentation
- Vue Components: Component Guide
- Migration Guide: Migration Guide
- Issues: GitHub Issues
Credits
- Originally extracted from the Marketplace project
- Built on top of Intervention Image
- Inspired by Spatie Media Library
- Vue components designed for Ionic Framework
Ready to get started? Check out our Quick Start Guide or dive into the full documentation.
