codprez/laravel-media-library

Media library with upload, variants, and selector UI for Laravel + Inertia + React.
348 1
Install
composer require codprez/laravel-media-library
Latest Version:v1.6.8
PHP:^8.2
License:MIT
Last Updated:May 29, 2026
Links: GitHub  ·  Packagist
Maintainer: obalaweb

Laravel Media Library

tests

A media library for Laravel applications built on Inertia.js and React: uploads, resized image variants, any storage disk (local or S3), a media manager screen and a picker you can drop into your own forms.

Features

  • Media management — upload (single or bulk), rename, delete, search, filter by type, multi-select and bulk delete.
  • Image variants — every uploaded raster image is resized into the variants you configure (thumbnail, medium, large by default) with cover, contain or stretch fits, in WebP/AVIF/JPEG/PNG. Powered by GD or Imagick, with no extra dependency.
  • Any disk — local, S3 or anything Flysystem supports. The disk, the file visibility and a CDN/base URL are configuration, and every record remembers where its file lives.
  • Works with remote disks — variants are rendered locally and then uploaded, so an S3 bucket behaves exactly like a local disk.
  • Queued variants — generate derivatives during the upload or push them to a queue.
  • Backfill commandphp artisan media:variants (re)builds variants for media uploaded before this feature existed.
  • Video thumbnails — optional single-frame extraction through ffmpeg.
  • Google Drive import — paste a public file or folder link, preview the files, import some or all of them in the background.
  • Model helpersgetOptimizedUrl(), getImageVariants(), getSrcset(), temporaryUrl() and a typed variants payload.
  • Inertia + React UI — packaged media manager page, upload modal and selector components.

Requirements

  • PHP 8.2+ with the GD extension (or Imagick), fileinfo and exif
  • Laravel 12.x | 13.x
  • Inertia.js with React for the bundled admin UI

Installation

composer require codprez/laravel-media-library

Publish the configuration and migrations, then migrate and link the storage directory (needed when files live on the local public disk):

php artisan vendor:publish --tag="media-library-config"
php artisan vendor:publish --tag="media-library-migrations"
php artisan migrate
php artisan storage:link

Configuration

config/media-library.php is optional as a whole: every value has a default, so a config file published by an older version of the package keeps working.

return [
    'disk' => env('MEDIA_LIBRARY_DISK', 'public'),
    'visibility' => env('MEDIA_LIBRARY_VISIBILITY', 'public'),
    'url' => env('MEDIA_LIBRARY_URL'),          // CDN or custom domain
    'path_prefix' => env('MEDIA_LIBRARY_PATH_PREFIX', 'media'),

    'uploads' => [
        'max_size' => 51200,                     // kilobytes
        'mime_types' => ['image/jpeg', 'image/png', /* ... */],
    ],

    'images' => [
        'enabled' => true,
        'driver' => null,                        // gd | imagick | null (auto-detect)
        'format' => 'webp',                      // webp | avif | jpeg | png | null
        'quality' => 82,
        'upscale' => false,
        'webp' => false,                         // extra full-size WebP copy
        'variants' => [
            'thumbnail' => ['width' => 300, 'height' => 300, 'fit' => 'cover'],
            'medium' => ['width' => 800, 'height' => null, 'fit' => 'contain'],
            'large' => ['width' => 1600, 'height' => null, 'fit' => 'contain'],
        ],
    ],

    'queue' => [
        'enabled' => false,
        'connection' => null,
        'name' => null,
    ],

    'video' => [
        'thumbnails' => true,
        'ffmpeg_path' => env('MEDIA_LIBRARY_FFMPEG_PATH', '/usr/bin/ffmpeg'),
    ],

    'user_model' => \App\Models\User::class,

    'google_drive' => [
        'api_key' => env('GOOGLE_DRIVE_API_KEY'),
        'allowed_mime_types' => ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/svg+xml'],
    ],

    'routing' => [
        'prefix' => 'builder',
        'middleware' => ['web', 'auth'],
    ],
];

Storing on S3 (or any other disk)

MEDIA_LIBRARY_DISK=s3
MEDIA_LIBRARY_VISIBILITY=public
MEDIA_LIBRARY_URL=https://cdn.example.com

Install the Flysystem adapter (composer require league/flysystem-aws-s3-v3) and configure the disk in config/filesystems.php as usual:

's3' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION'),
    'bucket' => env('AWS_BUCKET'),
    'url' => env('AWS_URL'),
],
  • disk — where uploads, variants and thumbnails are written.
  • visibilitypublic for public buckets and the local public disk, or private for locked-down buckets.
  • url — optional. When set, every media URL (variants included) is rewritten onto it, so moving to a CDN is a config change rather than a migration.
  • Every record stores the disk it was written to, so changing media-library.disk later never orphans existing files.
  • For private buckets, Media::temporaryUrl(60) returns a signed URL and null when the disk cannot produce one.

Image variants

Uploading an image generates every configured variant next to the original:

media/wb2ZgO3W.jpeg                             ← the original
media/thumbnail/wb2ZgO3W-thumbnail.webp
media/medium/wb2ZgO3W-medium.webp
media/large/wb2ZgO3W-large.webp

The paths and URLs are stored as JSON in the variants column (which supports any variant name, not just the built-in three) and mirrored into the legacy thumbnail_*, medium_*, large_* and webp_* columns.

$media->variants;          // ['thumbnail' => ['path' => ..., 'url' => ..., 'width' => 300, 'height' => 300, 'size' => 9123], ...]
$media->variantMap();      // the same payload, typed and filtered
$media->getOptimizedUrl('thumbnail');   // variant URL, or the original file
$media->getImageVariants();             // ['original' => ..., 'thumbnail' => ..., ...]
$media->getSrcset();                    // "https://… 300w, https://… 800w, https://… 1600w"
<img src="{{ $media->getOptimizedUrl('large') }}"
     srcset="{{ $media->getSrcset() }}"
     width="{{ $media->width }}"
     height="{{ $media->height }}"
     alt="">

Drivers and formats

Driver Decodes Encodes
gd (default, auto-detected first) JPEG, PNG, WebP, BMP, AVIF* JPEG, PNG, WebP, AVIF*
imagick whatever ImageMagick supports whatever ImageMagick supports

* when the extension was compiled with that format. If the requested images.format is unavailable, the driver falls back to the source format and then to PNG, so an upload never fails because of a missing codec.

Formats that cannot be rasterised safely are stored untouched — SVG (scalable) and GIF (possibly animated) — and fall back to the original file through getOptimizedUrl().

Fits

Fit Behaviour
cover Crops the source to the target aspect ratio, then scales to exactly width x height.
contain Scales the whole image to fit the box; leave height null to scale by width only.
stretch Uses width x height verbatim, ignoring the aspect ratio.

With images.upscale set to false (the default) a variant is never larger than the original. JPEGs are auto-rotated from their EXIF orientation and transparency is preserved for WebP/PNG/AVIF (and flattened onto white for JPEG).

Regenerating variants

php artisan media:variants               # only records that have no variants yet
php artisan media:variants --force       # rebuild everything
php artisan media:variants --id=12 --id=15
php artisan media:variants --type=video  # re-extract video thumbnails

Per record, from code:

$media->regenerateVariants();   // true when something was generated

or over HTTP: POST /{prefix}/media/{media}/variants.

HTTP API

Routes are registered under the configured routing.prefix (/builder/media by default) behind routing.middleware (['web', 'auth'] by default). Requests that expect JSON receive JSON; Inertia requests receive redirects and flash messages.

Method URI Description
GET /{prefix}/media Paginated listing (search, type, per_page — capped at 100). Renders the media manager page for Inertia.
POST /{prefix}/media Upload one (file) or many (files[]) files. Answers 201 with media, media_items and count.
GET /{prefix}/media/{media} A single record.
PUT /{prefix}/media/{media} Rename (name).
POST /{prefix}/media/{media}/variants Regenerate the variants of one record.
DELETE /{prefix}/media/{media} Delete one record and its files.
DELETE /{prefix}/media Bulk delete (ids[]).
curl -X POST https://example.com/builder/media \
     -H "Accept: application/json" \
     -F "file=@photo.jpg"
{
  "success": true,
  "count": 1,
  "media": {
    "id": 1,
    "disk": "s3",
    "url": "https://cdn.example.com/media/wb2ZgO3W.jpg",
    "is_image": true,
    "width": 1600,
    "height": 900,
    "variants": {
      "thumbnail": {
        "path": "media/thumbnail/wb2ZgO3W-thumbnail.webp",
        "url": "https://cdn.example.com/media/thumbnail/wb2ZgO3W-thumbnail.webp",
        "width": 300,
        "height": 300,
        "size": 9123
      }
    },
    "srcset": "https://cdn.example.com/media/thumbnail/wb2ZgO3W-thumbnail.webp 300w, …"
  }
}

Using the package in your application

use Codprez\MediaLibrary\Services\MediaUploader;

$media = app(MediaUploader::class)->upload($request->file('image'));

$media->getOptimizedUrl('medium');
$media->getSrcset();

Register a file that already lives on a disk (an export, an import, a sync):

app(MediaUploader::class)->register('exports/report.pdf', [
    'name' => 'Q3 report',
    'original_name' => 'report.pdf',
    'mime_type' => 'application/pdf',
]);

Listen for the Media model in your own code as usual — it is a plain Eloquent model with SoftDeletes, the variants cast and a url accessor that follows the configured CDN.

Google Drive import

GOOGLE_DRIVE_API_KEY=your_google_api_key

Paste a public file or folder link in the media manager, preview what it finds and import the files you select. Large folders are imported in the background through the queue, with progress shown in the UI.

Frontend components

The React components ship as source under resources/js:

// Either alias the package source in your bundler, e.g. in vite.config.js:
//   resolve: { alias: { '@medialibrary': path.resolve('vendor/codprez/laravel-media-library/resources/js') } }
import { MediaSelector } from "@medialibrary/components/media-selector";

// …or import it relatively, since the package ships raw source.
import { MediaSelector } from "../../vendor/codprez/laravel-media-library/resources/js/components/media-selector";
Component Purpose
MediaSelector Modal picker with search, type filters, upload and Google Drive import.
ImageUpload Single image field that opens the picker or accepts a drop.
DocumentUpload The same, for documents.

Tailwind 4 users: add the package source to your stylesheet so the utility classes used by the bundled UI are generated: @source '../../vendor/codprez/laravel-media-library/resources/js';

Testing and formatting

composer test        # vendor/bin/phpunit
composer lint        # vendor/bin/pint
composer lint:check  # vendor/bin/pint --test

The suite runs on Orchestra Testbench and covers variant generation, disk handling (including a faked S3 disk), the HTTP API and the backfill command (GitHub Actions matrix: PHP 8.2–8.4, Laravel 12 and 13).

Upgrading from 1.6

  1. composer update codprez/laravel-media-library
  2. php artisan vendor:publish --tag="media-library-config" --force (optional — every new key has a default, so you can also add only the ones you want)
  3. php artisan migrate — adds the nullable disk and variants columns
  4. php artisan media:variants — backfills variants for existing images

There are no breaking changes: existing columns, routes, components and the behaviour of getOptimizedUrl() / getImageVariants() are unchanged.

License

The MIT License (MIT). Please see License File for more information.

Related Packages

erlandmuchasaj/laravel-file-uploader

A simple package to help you easily upload files to your laravel project.

9,666 13
okamal/laravel-media-zone

Elegant polymorphic media uploads for Laravel with Inertia.js and zone-based org...

3 2
akira/laravel-spectra

Illuminate your API — interactive inspector for Laravel 12 with Inertia + React

13 4
ahir/bookcase

Laravel Media Library

40 0
spescina/mediabrowser

Laravel packages that provide a basic user interface for browsing a server folde...

575 14