laravel-mediaforge maintained by webcimes
webcimes/laravel-mediaforge
Store images and files directly in your existing model columns — no separate media library table required.
Powered by Intervention Image, this Laravel package lets you upload a file once and automatically generate multiple formats (thumbnail, WebP, watermark, etc.), each saved as a structured entry you can store in any JSON/text column of your choosing.
Why this package?
Most media libraries (like Spatie Media Library) introduce a dedicated media table that links files to models through a polymorphic relationship. This works great for complex scenarios, but adds overhead when you just want to attach one or a few images to a model.
With this package:
- Image data (path, dimensions, format config) is stored directly in whichever column you choose — a JSON column, a
textcolumn, even inside a JSON API response. - No extra table, no polymorphic join, no extra migration.
- The upload result is a plain PHP array — store it anywhere, serialize it however you like.
- Regenerate derivative formats at any time from the stored original.
Features
- Fluent
ImageFormatbuilder — chain transforms in a readable, IDE-friendly way - Multi-format processing in one upload (default + thumb + WebP, all at once)
- All formats of the same upload grouped in a single folder for easy management
- ULID-based unique naming (collision-proof, chronologically sortable, URL-safe)
- Plain PHP array output — no model binding required
- Regenerate derivative formats from the stored original at any time
- Works with any Laravel filesystem disk (local, S3, SFTP, …)
Requirements
- PHP 8.2+
- Laravel 11, 12, or 13
- Intervention Image 3.x — requires either the GD or Imagick PHP extension
Installation
composer require webcimes/laravel-mediaforge
Publish the config file:
php artisan vendor:publish --tag="mediaforge-config"
Basic usage
use Webcimes\LaravelMediaforge\ImageFormat;
use Webcimes\LaravelMediaforge\Facades\MediaForge;
// Upload and generate two formats in one call:
$imageData = MediaForge::upload(
$request->file('cover'),
'public', // disk
'products', // base path inside the disk
[
ImageFormat::make('default')->scaleDown(1920, 1080)->quality(80)->extension('webp'),
ImageFormat::make('thumb')->cover(400, 300)->quality(65)->extension('webp'),
],
);
// Store $imageData directly in your model column (cast it to 'array' or 'json'):
$product->update(['cover' => $imageData]);
$imageData is a plain array — store it in any JSON column:
// $imageData:
[
'default' => [
'disk' => 'public',
'path' => 'products/my-cover_01jq8z.../default.webp',
'width' => 1920,
'height' => 1080,
'alt' => 'my-cover',
'_config' => [...], // stored for regeneration
],
'thumb' => [
'disk' => 'public',
'path' => 'products/my-cover_01jq8z.../thumb.webp',
'width' => 400,
'height'=> 300,
'alt' => 'my-cover',
'_config' => [...],
],
]
ImageFormat reference
| Method | Description |
|---|---|
->disk('s3') |
Override the storage disk for this format |
->path('media/thumbs') |
Override the storage directory |
->extension('webp') |
Convert to a specific format |
->quality(75) |
Encoding quality 1–100 (JPEG, WebP, AVIF, HEIC, TIFF) |
->filename('hero') |
Override the file name (without extension) |
->suffix('_2x') |
Append a suffix before the extension |
->resize(w, h) |
Exact dimensions — no ratio preservation |
->resizeDown(w, h) |
Same — only shrinks |
->scale(w, h?) |
Proportional fit — preserves ratio |
->scaleDown(w, h?) |
Same — only shrinks |
->cover(w, h) |
Center-crop to exact dimensions |
->coverDown(w, h) |
Same — only shrinks |
->text('Draft', [...]) |
Text overlay (requires a TTF font — see config) |
->watermark('/path/logo.png', [...]) |
Image watermark overlay |
->customAttributes([...]) |
Custom metadata stored alongside this format entry |
Regenerate a format
Re-process derivatives from the stored original at any time — useful when you change the design:
$updated = MediaForge::regenerate($product->cover, [
ImageFormat::make('thumb')->cover(200, 200)->extension('avif'),
]);
$product->update(['cover' => $updated]);
Delete files
// Deletes all files referenced in the stored entry:
MediaForge::delete($product->cover, 'public');
Custom base name
The upload folder name is {slug}_{ulid} by default. Override it:
// Auto (default): slug + ULID → my-photo_01jq8z...
MediaForge::upload($file, 'public', 'uploads', $formats);
// ULID only (no slug) → 01jq8z...
MediaForge::upload($file, 'public', 'uploads', $formats, '');
// Custom prefix + ULID → product-hero_01jq8z...
MediaForge::upload($file, 'public', 'uploads', $formats, 'product-hero');
Configuration
After publishing (php artisan vendor:publish --tag="mediaforge-config"):
return [
// Image processing driver:
// 'gd' — Built into PHP, works on every host. Default.
// 'imagick' — Requires ext-imagick. Better quality, native AVIF/HEIC. Recommended if available.
// 'vips' — Requires: composer require intervention/image-driver-vips + libvips.
// Fastest and lowest memory, but rarely available on shared hosting.
'driver' => 'gd', // switch to 'imagick' if available on your server
// Text overlay defaults. Any key passed to ImageFormat::text([...]) overrides these.
// The default font is Montserrat Regular (TTF) bundled in the package — no setup needed.
// To use a custom font, set an absolute path to a TTF or OTF file:
'text' => [
'font' => null, // defaults to Montserrat TTF bundled in vendor; override: resource_path('fonts/my-font.ttf')
'size' => 48,
'color' => 'rgba(255, 255, 255, .75)',
'align' => 'center',
'valign' => 'middle',
'angle' => 0,
'wrap' => 0, // max line width in px before wrapping; 0 = no wrap
],
// Watermark overlay defaults.
'watermark' => [
'position' => 'center',
'x' => 0,
'y' => 0,
'opacity' => 75,
],
];
License
MIT