A comprehensive Laravel package for Imagor integration. Generate optimized, signed image URLs with fluent API including resizing, quality control, visual effects, and advanced processing options. Originally forked from https://github.com/imsus/laravel-imgproxy, which deserves most credit :)
- Laravel integration for Imagor
- Basic Usage
- Laravel Integration
- API Reference
- Image Processing Recipes
- Development Setup
- Troubleshooting
- File Sizes
- Changelog
- Credits
- License
- 🚀 Fluent API - Clean, chainable method syntax
- 🔒 Secure URLs - HMAC signed URLs with configurable keys
- 🎨 Visual Effects - Blur, sharpen, brightness, contrast, saturation adjustments
- ⚡ Quality Control - Fine-tune compression and output formats
- 🔧 Flexible Resizing - Multiple resize modes with smart cropping
- 🧩 Laravel Integration - Service provider, facade, and helper function
- ✅ Type Safe - PHP 8.2+ with comprehensive validation
- 🧪 Well Tested - Comprehensive test suite with workbench integration
You can install the package via composer:
composer require sandstorm/laravel-imagor
You can publish the config file with:
php artisan vendor:publish --tag="imagor-config"
To set up Imagor via docker-compose.yaml
, use the following snippet:
services:
laravel:
# ...
# we assume the laravel application lives in "/app" in the Docker container
volumes:
- laravel-storage:/app/storage
environment:
IMAGOR_PUBLIC_BASE_URL: http://127.0.0.1:8091
IMAGOR_INTERNAL_BASE_URL: http://imagor:8091
IMAGOR_SECRET: UNSAFE_DEV_SECRET
IMAGOR_SIGNER_TYPE: sha256
IMAGOR_SIGNER_TRUNCATE: 40
# use the mozjpeg version from docker-hub.sandstorm.de/docker-infrastructure/imagor:v1.5.16-mozjpeg
# See https://gitlab.sandstorm.de/docker-infrastructure/imagor/container_registry for the currently built versions.
#
# to get bigger JPEG files - alternatively you can also use the official shumc/imagor image from docker hub
imagor:
image: docker-hub.sandstorm.de/docker-infrastructure/imagor:v1.5.16-mozjpeg
ports:
- ${IMAGOR_PORT:-8091:8091}
environment:
# if things do not work, enable debugging:
# DEBUG: 1
PORT: 8091
IMAGOR_SECRET: UNSAFE_DEV_SECRET
IMAGOR_SIGNER_TYPE: sha256
IMAGOR_SIGNER_TRUNCATE: 40
IMAGOR_CACHE_HEADER_TTL: 8760h
IMAGOR_PROCESS_CONCURRENCY: 25
IMAGOR_PROCESS_QUEUE_SIZE: 200
SERVER_ADDRESS: imagor
SERVER_CORS: true
# crucial if files contain special characters, e.g. for Laravel temporary uploads
# -> disables mangling of filenames during loading
FILE_SAFE_CHARS: '--'
FILE_LOADER_BASE_DIR: '/app'
FILE_RESULT_STORAGE_BASE_DIR: '/mnt/imagor_cache/results'
FILE_STORAGE_BASE_DIR: '/mnt/imagor_cache/input'
VIPS_MAX_ANIMATION_FRAMES: 1
VIPS_MAX_FILTER_OPS: 20
VIPS_MAX_WIDTH: 10000
VIPS_MAX_HEIGHT: 10000
VIPS_MAX_RESOLUTION: 100000000
# bigger images (optional)
VIPS_MOZJPEG: 1
volumes:
- laravel-storage:/app/storage
Finally, can call the URL /__imagor-configtest to check if Imagor is configured correctly. This example copies some pre-defined images to the storage folder and then loads it through Imagor to check if everything is wired correctly.
if you want to build the image yourself, see see the ./laravel-imagor folder which contains the sources of this docker image.
Mount the ./storage
folder of your Laravel application to the same folder in the Imagor container;
so if the storage folder is located at /app/storage
, you should mount it to /app/storage
in the Imagor container
as well.
Then, you can use FILE_LOADER_BASE_DIR='/app'
to load images from the mounted storage folder.
Adjust the path mapping if needed via the path_map
config option:
'path_map' => [
// the key is the original (Laravel) path prefix, the value is the corresponding Imagor path prefix AFTER the FILE_LOADER_BASE_DIR.
// so /storage on the right side resolves to /app/storage on the file system of the Imagor container
storage_path() => '/storage',
]
// Generate URL using helper function
$url = imagor()
->resize(width: 400, height: 300)
->uriFor('https://example.com/image.jpg');
NOTE: the image URL to be processed is passed at the END of the chain, to be able to use the same instance of the
Imagor
class for multiple image processing operations:
$resizeOp = imagor()
->resize(width: 400, height: 300);
$url1 = $resizeOp->uriFor('https://example.com/image.jpg');
$url2 = $resizeOp->uriFor('https://example.com/foo.jpg');
NOTE: the Imagor
class is immutable, so you always need to assign the result of a method call to a variable:
$imagor = imagor();
// ❌ WILL NOT WORK ❌ because a new object is returned
$imagor->resize(width: 400, height: 300);
$imagor->uriFor('https://example.com/image.jpg');
// ✅ Instead, do the following:
$imagor = $imagor->resize(width: 400, height: 300);
$imagor->uriFor('https://example.com/image.jpg');
The following methods exist for accessing the Imagor object:
- inject
Sandstorm\LaravelImagor\ImagorFactory
, and call->new()
to get a new instance of theImagor
class - inject
Sandstorm\LaravelImagor\Imagor
- you'll get a new instance of theImagor
class every time - use the
imagor()
helper function
If in doubt, use one of the injections.
// Basic resizing
$url = imagor()
->resize(width: 400, height: 300)
->uriFor($imageUrl);
// Fit image within dimensions (preserves aspect ratio)
$url = imagor()
->resize(width: 400, height: 300)
->fitIn()
->uriFor($imageUrl);
// Force stretch to exact dimensions (does NOT preserve aspect ratio)
$url = imagor()
->resize(width: 400, height: 300)
->stretch()
->uriFor($imageUrl);
// Smart cropping with focal point detection
$url = imagor()
->resize(width: 400, height: 300)
->smart()
->uriFor($imageUrl);
// Manual cropping (left, top, right, bottom)
$url = imagor()
->crop(10, 10, 300, 200)
->uriFor($imageUrl);
// Set JPEG quality
$url = imagor()->resize(width: 400)->quality(85)->uriFor($imageUrl);
// Convert to different formats
$webpUrl = imagor()->resize(width: 400)->format('webp')->uriFor($imageUrl);
$avifUrl = imagor()->resize(width: 400)->format('avif')->uriFor($imageUrl);
$pngUrl = imagor()->resize(width: 400)->format('png')->uriFor($imageUrl);
$url = imagor()
->resize(width: 500, height: 300)
->blur(2.0) // Blur effect
->sharpen(1.5) // Sharpen details
->brightness(20) // Increase brightness
->contrast(110) // Enhance contrast (percentage)
->saturation(120) // Boost saturation (percentage)
->uriFor($imageUrl);
// Flip images
$url = imagor()
->resize(width: 400, height: 300)
->flipHorizontally()
->flipVertically()
->uriFor($imageUrl);
// Add padding (left, top, right, bottom)
$url = imagor()
->resize(width: 400, height: 300)
->padding(10, 10, 10, 10)
->uriFor($imageUrl);
// Set alignment for cropping
$url = imagor()
->resize(width: 400, height: 300)
->hAlign('left') // 'left', 'right', 'center'
->vAlign('top') // 'top', 'bottom', 'middle'
->uriFor($imageUrl);
If, for some reason, you need to access the image binary data, you can use ->imageBinaryDataFor()
instead of
->uriFor()
:
$binaryData = imagor()
->resize(width: 400, height: 300)
->format('jpeg')
->imageBinaryDataFor($imageUrl);
echo '<img src="data:image/jpeg;base64,' . base64_encode($binaryData) . '" />';
before:
<img width="300" src="{{ $mediaFile->temporaryUrl() }}" />
after:
<img width="300" src="{{ imagor()->resize(300)->uriFor($mediaFile->getPathname()) }}" />
before:
<img src="{{ asset('storage/' . $post->media_files[0]) }}" />
after:
<img src="{{ imagor()->resize(300)->uriFor(Storage::disk('public')->path($post->media_files[0])) }}">
TODO: see if we can make this work a bit simpler :)
before:
FileUpload::make('media_files')
->acceptedFileTypes(['image/*'])
->rules(['image', 'max:10240']) // max 10MB per file
->disk('public')
->visibility('public')
after:
use Sandstorm\LaravelImagor\Filament\Components\ImagorFileUpload;
ImagorFileUpload::make('media_files')
->acceptedFileTypes(['image/*'])
->rules(['image', 'max:10240']) // max 10MB per file
// not required anymore, imagor also works with private files
->disk('public')
// not required anymore, imagor also works with private files
->visibility('public')
// specify which size is needed
->imageProcessor(imagor()->resize(100)),
// same logic, different syntax (without global function)
->imageProcessor(fn(ImagorPathBuilder $imagor) => $imagor->resize(100)),
Create custom Blade directives for common use cases:
// In AppServiceProvider::boot()
use Illuminate\Support\Facades\Blade;
Blade::directive('imagorWide', function ($expression) {
return "<?php echo imagor()->resize(width: 400)->uriFor($expression); ?>";
});
Blade::directive('avatar', function ($expression) {
return "<?php echo imagor()->resize(width: 150, height: 150)->smart()->uriFor($expression); ?>";
});
{{-- Usage in Blade templates --}}
<img src="@imagorWide($product->image)" alt="Product">
<img src="@avatar($user->avatar)" alt="User Avatar">
Method | Parameters | Description |
---|---|---|
resize(int $width, int $height) |
Width, height in pixels | Set image dimensions |
crop(int $a, int $b, int $c, int $d) |
Coordinates | Manual crop (left, top, right, bottom) |
fitIn() |
- | Fit image within dimensions |
stretch() |
- | Force resize without aspect ratio |
smart() |
- | Smart focal point detection |
trim() |
- | Remove surrounding whitespace |
flipHorizontally() |
- | Flip image horizontally |
flipVertically() |
- | Flip image vertically |
padding(int $left, int $top, int $right, int $bottom) |
Padding values | Add padding around image |
hAlign(string $align) |
'left', 'right', 'center' | Horizontal alignment |
vAlign(string $align) |
'top', 'bottom', 'middle' | Vertical alignment |
quality(int $quality) |
0-100 | Set JPEG quality |
format(string $format) |
Format string | Set output format |
blur(float $sigma) |
≥0.0 | Apply blur effect |
sharpen(float $sigma) |
≥0.0 | Apply sharpen effect |
brightness(int $amount) |
-255 to 255 | Adjust brightness |
contrast(int $amount) |
Percentage | Adjust contrast |
saturation(int $amount) |
Percentage | Adjust saturation |
addFilter(string $name, ...$args) |
Filter name and args | Add custom filter |
uriFor(string $sourceImage) |
Image URL | Generate final URL |
jpeg
- JPEG formatpng
- PNG formatgif
- GIF formatwebp
- WebP formatavif
- AVIF formatjxl
- JPEG XL formattiff
- TIFF formatjp2
- JPEG 2000 format
// Portrait enhancement
$enhancedPortrait = imagor()
->resize(width: 600, height: 800)
->smart()
->brightness(8) // Slightly brighter
->contrast(110) // Enhanced contrast
->saturation(105) // Subtle saturation boost
->sharpen(0.8) // Gentle sharpening
->quality(92)
->uriFor($portrait);
// Vintage effect
$vintageEffect = imagor()
->resize(width: 600, height: 400)
->saturation(70) // Reduced saturation
->contrast(90) // Lower contrast
->brightness(-10) // Slightly darker
->quality(85)
->uriFor($image);
// Clean product photos
$productClean = imagor()
->resize(width: 800, height: 800)
->fitIn()
->trim() // Remove whitespace
->brightness(15) // Bright and clean
->contrast(110) // Good contrast
->sharpen(1.5) // Sharp product details
->quality(95) // High quality for products
->format('webp')
->uriFor($product);
# Run all tests
composer test
# Run with coverage
composer test-coverage
The package includes a comprehensive workbench environment for interactive testing:
# Start the workbench server
composer start
# Or build separately and serve
composer build
php vendor/bin/testbench serve
Now access the Visual Test Suite at http://localhost:8000/imgproxy-visual-test
Problem: Getting "unsafe" URLs instead of signed URLs
http://localhost:8000/unsafe/400x300/...
Solution: Ensure IMAGOR_SECRET
is set in your .env
file.
Problem: Images not loading/404 errors Solutions:
- Verify Imagor server is running at the configured base URL
- Check source image URLs are accessible
- Ensure Imagor server can reach source URLs (firewall/network issues)
Problem: Poor image quality
Solutions:
- Increase quality setting:
->quality(90)
- Use appropriate output format:
->format('webp')
- Avoid excessive sharpening:
->sharpen(1.0)
instead of higher values
imagor_docker, with VIPS_MOZJPEG: 0; or shumc/imagor:latest
(no mozjpeg)
docker compose down -v
docker compose up -d
+---------------------+--------+------------+----------+
| version | format | dimensions | size |
+---------------------+--------+------------+----------+
| original | jpeg | orig | 6.3 MB |
| optimized_no_change | jpeg | orig | 3.7 MB |
| optimized_quality95 | jpeg | orig | 13.8 MB |
| optimized_quality80 | jpeg | orig | 4.3 MB |
| optimized_quality50 | jpeg | orig | 2.5 MB |
| optimized_no_change | jpeg | 600x600 | 44.3 KB |
| optimized_quality95 | jpeg | 600x600 | 161.9 KB |
| optimized_quality80 | jpeg | 600x600 | 50.8 KB |
| optimized_quality50 | jpeg | 600x600 | 29.7 KB |
| optimized_no_change | webp | orig | 1.5 MB |
| optimized_quality95 | webp | orig | 6.2 MB |
| optimized_quality80 | webp | orig | 1.9 MB |
| optimized_quality50 | webp | orig | 1.1 MB |
| optimized_no_change | webp | 600x600 | 28.6 KB |
| optimized_quality95 | webp | 600x600 | 97.4 KB |
| optimized_quality80 | webp | 600x600 | 35.6 KB |
| optimized_quality50 | webp | 600x600 | 20.5 KB |
+---------------------+--------+------------+----------+
imagor_docker, with VIPS_MOZJPEG: 1
f.e. the image ghcr.io/cshum/imagor-mozjpeg:docker-variants from cshum/imagor#456 (comment)
docker compose down -v
docker compose up -d
vendor/bin/testbench imagor:benchmark-image-sizes
+---------------------+--------+------------+----------+
| version | format | dimensions | size | without mozjpeg (from above)
+---------------------+--------+------------+----------+
| original | jpeg | orig | 6.3 MB |
| optimized_no_change | jpeg | orig | 2.5 MB | 3.7 MB (32.4% bigger)
| optimized_quality95 | jpeg | orig | 10.6 MB | 13.8 MB (23.2% bigger)
| optimized_quality80 | jpeg | orig | 3.1 MB | 4.3 MB (27.9% bigger)
| optimized_quality50 | jpeg | orig | 1.5 MB | 2.5 MB (40.0% bigger)
| optimized_no_change | jpeg | 600x600 | 35.9 KB | 44.3 KB (19.0% bigger)
| optimized_quality95 | jpeg | 600x600 | 152.2 KB | 161.9 KB (6.0% bigger)
| optimized_quality80 | jpeg | 600x600 | 42.8 KB | 50.8 KB (15.7% bigger)
| optimized_quality50 | jpeg | 600x600 | 22.1 KB | 29.7 KB (25.6% bigger)
| optimized_no_change | webp | orig | 1.5 MB |
| optimized_quality95 | webp | orig | 6.2 MB |
| optimized_quality80 | webp | orig | 1.9 MB |
| optimized_quality50 | webp | orig | 1.1 MB |
| optimized_no_change | webp | 600x600 | 28.6 KB |
| optimized_quality95 | webp | 600x600 | 97.4 KB |
| optimized_quality80 | webp | 600x600 | 35.6 KB |
| optimized_quality50 | webp | 600x600 | 20.5 KB |
+---------------------+--------+------------+----------+
Please see CHANGELOG for more information on what has changed recently.
- Imam Susanto for the original package
- Sandstorm Media for the Imagor fork
- All Contributors
The MIT License (MIT). Please see License File for more information.