Search by

edemsky.m / laravel-media-collections

edemsky

Media collections with per-collection conversions on top of spatie/laravel-medialibrary.

Package info

gitlab.com/edemsky.m/laravel-media-collections

Issues

pkg:composer/edemsky.m/laravel-media-collections

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

v0.1.1 2026-10-28 12:50 UTC

This package is auto-updated.

Last update: 2026-10-28 18:44:58 UTC


README

Latest Version Downloads License

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-medialibrary 11.x
  • spatie/laravel-translatable 6.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:

MethodURIAction
GET/{prefix}/{model}/{id}/collections/{collection}/mediaList media
POST.../mediaUpload one file
POST.../media/manyUpload many files
POST.../media/urlUpload from URL
GET/PUT/DELETE/{prefix}/media/{media}Show/update/delete
POST/{prefix}/media/{media}/fileReplace file
PUT/{prefix}/media/sortSort media
GET/POST/PUT/DELETE/{prefix}/collections, /{prefix}/typesCatalog CRUD
GET/POST/DELETE/{prefix}/{model}/{id}/collections...Bind collections to a model (database mode)
PUT.../collections, .../collections/sortSync/sort bound collections
GET/{prefix}/{model}/{id}/attachedList attached media
POST/DELETE.../attached/{media}Attach/detach
PUT.../attachedSync 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

KeyDefaultPurpose
diskpublicDisk for stored files
binding.modedeclarativedeclarative or database
binding.cache.enabledfalsePersistent collection resolver cache
media_modelSpatie MediaSet to package Models\Media for URL caching
conversionspresetsConversion key map
conversion_defaults.webpfalseForce WebP for image conversions
first_media.collectionnullOverride first-media collection
queue.enabledfalseDispatch media update jobs
http.enabledfalseRegister HTTP routes

Documentation

Testing

composer update
vendor/bin/pest
vendor/bin/pint --test

License

MIT. See LICENSE.