edemsky.m / laravel-media-collections
Media collections with per-collection conversions on top of spatie/laravel-medialibrary.
Package info
gitlab.com/edemsky.m/laravel-media-collections
pkg:composer/edemsky.m/laravel-media-collections
Requires
- php: ^8.2
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- spatie/image: ^3.0
- spatie/laravel-medialibrary: ^11.0
- spatie/laravel-translatable: ^6.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Media collections with per-collection conversions on top of
spatie/laravel-medialibrary.
Collections are database records, not hard-coded names. Each collection has a type, allowed file extensions, JSON settings and its own conversion list. Models declare which collections they expose, either in code or through a pivot table.
Features
- Database-driven collections and collection types.
- Declarative or database-backed model binding.
- Config-driven conversion registry with presets and per-collection parameters.
- Optional HTTP API for media, attached media and collection catalogs.
- Optional conversion URL caching, CDN mapping and first-media denormalization.
- Regeneration commands for conversions and cached URLs.
Requirements
- PHP 8.2+
- Laravel 11, 12 or 13
spatie/laravel-medialibrary11.xspatie/laravel-translatable6.x
Installation
composer require edemsky.m/laravel-media-collections
For local development against a checkout, use a path repository:
composer config repositories.media-collections path /path/to/laravel-media-collections
composer require edemsky.m/laravel-media-collections:@dev
Publish the configuration and migrations:
php artisan vendor:publish --tag=media-collections-config
php artisan vendor:publish --tag=media-collections-migrations
Optional translations (English is the default, Russian is included):
php artisan vendor:publish --tag=media-collections-translations
Optional default collection types:
php artisan vendor:publish --tag=media-collections-seeders
php artisan db:seed --class="Database\\Seeders\\MediaCollectionTypeSeeder"
This creates the images, files and videos types. The seeder is idempotent and can be run again.
You can also run the package seeder directly without publishing:
php artisan db:seed --class="EdemskyM\\LaravelMediaCollections\\Database\\Seeders\\MediaCollectionTypeSeeder"
Publish the Spatie media migration before running the package migrations: the pivot table has a
foreign key to media.
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate
The package is auto-discovered through extra.laravel.providers.
Quick start
Create a collection type and a collection. You can do this with models, seeders or the optional HTTP catalog endpoints:
use EdemskyM\LaravelMediaCollections\Enums\MediaType;
use EdemskyM\LaravelMediaCollections\Models\MediaCollection;
use EdemskyM\LaravelMediaCollections\Models\MediaCollectionType;
$type = MediaCollectionType::create([
'name' => ['en' => 'Images', 'ru' => 'Изображения'],
'slug' => 'images',
'library_type' => MediaType::images,
'mime_types' => ['jpg', 'jpeg', 'png', 'webp'],
]);
MediaCollection::create([
'name' => ['en' => 'Product images', 'ru' => 'Изображения товара'],
'slug' => 'product',
'media_collection_type_id' => $type->id,
'conversions' => ['Thumb', 'Small', 'Large'],
'custom_properties' => ['maxFileSize' => '20MB'],
'active' => true,
]);
Declarative binding
use EdemskyM\LaravelMediaCollections\Concerns\InteractsWithMediaCollections;
use EdemskyM\LaravelMediaCollections\Contracts\HasMediaCollectionsInterface;
use Spatie\MediaLibrary\HasMedia;
class Product extends Model implements HasMedia, HasMediaCollectionsInterface
{
use InteractsWithMediaCollections;
public function mediaCollections(): array
{
return ['product'];
}
}
Database binding
use EdemskyM\LaravelMediaCollections\Concerns\HasDatabaseMediaCollections;
use EdemskyM\LaravelMediaCollections\Concerns\InteractsWithMediaCollections;
use EdemskyM\LaravelMediaCollections\Contracts\HasMediaCollectionsInterface;
use Spatie\MediaLibrary\HasMedia;
class Product extends Model implements HasMedia, HasMediaCollectionsInterface
{
use HasDatabaseMediaCollections;
use InteractsWithMediaCollections;
}
config(['media-collections.binding.mode' => 'database']);
$product->attachMediaCollection('product', sort: 10);
$product->syncMediaCollections(['product', 'documents']);
Both modes use the same HasMediaCollectionsInterface contract, so application code does not change.
Uploading
use EdemskyM\LaravelMediaCollections\Actions\AddMediaAction;
use EdemskyM\LaravelMediaCollections\DTO\MediaUploadData;
$media = app(AddMediaAction::class)->handle(
$product,
MediaCollection::where('slug', 'product')->firstOrFail(),
new MediaUploadData(
file: $request->file('file'),
customProperties: ['alt' => 'Product photo'],
),
);
The action validates the file against the collection type, extension list, size and dimension settings.
Conversions
Register conversion classes in config/media-collections.php:
'conversions' => [
'Thumb' => ThumbConversion::class,
'Small' => SmallConversion::class,
'ProductHero' => App\Media\ProductHeroConversion::class,
],
Store keys in the collection's conversions JSON:
["Thumb", {"key": "Small", "params": {"width": 320, "height": 240, "retina": true}}]
Custom conversions implement EdemskyM\LaravelMediaCollections\Conversions\MediaConversion:
class ProductHeroConversion implements MediaConversion
{
public function apply(HasMedia $model, MediaCollection $collection): void
{
$model->addMediaConversion('hero')
->performOnCollections($collection->getCollectionName())
->width(1600);
}
}
Legacy conversions that implement MediaConversionInterface::get() are wrapped automatically.
Optional HTTP API
Set http.enabled and map URL slugs to model classes:
'http' => [
'enabled' => true,
'prefix' => 'api/media',
'middleware' => ['api'],
'authorization' => [
'enabled' => false,
'authorizer' => GateMediaAuthorizer::class,
'abilities' => GateMediaAuthorizer::defaultAbilities(),
],
'models' => [
'product' => App\Models\Product::class,
],
],
Main endpoints:
| Method | URI | Action |
|---|---|---|
GET | /{prefix}/{model}/{id}/collections/{collection}/media | List media |
POST | .../media | Upload one file |
POST | .../media/many | Upload many files |
POST | .../media/url | Upload from URL |
GET/PUT/DELETE | /{prefix}/media/{media} | Show/update/delete |
POST | /{prefix}/media/{media}/file | Replace file |
PUT | /{prefix}/media/sort | Sort media |
GET/POST/PUT/DELETE | /{prefix}/collections, /{prefix}/types | Catalog CRUD |
GET/POST/DELETE | /{prefix}/{model}/{id}/collections... | Bind collections to a model (database mode) |
PUT | .../collections, .../collections/sort | Sync/sort bound collections |
GET | /{prefix}/{model}/{id}/attached | List attached media |
POST/DELETE | .../attached/{media} | Attach/detach |
PUT | .../attached | Sync attached media |
When authorization is enabled, gates receive a MediaAccessContext with operation, model,
collection and media. Ability names are configurable:
Gate::define('uploadMedia', function (User $user, MediaAccessContext $context) {
return $user->can('update', $context->model)
&& $context->collection?->slug !== 'private';
});
For complex business rules, replace GateMediaAuthorizer with a custom MediaAuthorizer
implementation via authorization.authorizer.
Commands
php artisan media-collections:regenerate --ids=1,2 --force
php artisan media-collections:regenerate --collection=product --chunk=100 --without-events
php artisan media-collections:regenerate-urls --chunk=100 --without-events --force
Supported filters: --model-type, --model-id, --collection, --ids, --chunk,
--starting-from-id, --exclude-starting-id, --without-events, --force.
Configuration highlights
| Key | Default | Purpose |
|---|---|---|
disk | public | Disk for stored files |
binding.mode | declarative | declarative or database |
binding.cache.enabled | false | Persistent collection resolver cache |
media_model | Spatie Media | Set to package Models\Media for URL caching |
conversions | presets | Conversion key map |
conversion_defaults.webp | false | Force WebP for image conversions |
first_media.collection | null | Override first-media collection |
queue.enabled | false | Dispatch media update jobs |
http.enabled | false | Register HTTP routes |
Documentation
Testing
composer update
vendor/bin/pest
vendor/bin/pint --test
License
MIT. See LICENSE.