A powerful Laravel package for managing images with responsive breakpoints, automatic optimization, and contextual configurations. Store and link images to your models with advanced features like automatic WebP generation, responsive image versions, and flexible image contexts.
⚠️ Caution: V3 is a complete rewrite of the package and logic. Please take a look at the upgrade guide before upgrading from v2.x to v3.x.
This package uses spatie/image for image manipulations, so it requires the GD or Imagick PHP extension.
You can install the package via composer:
composer require outerweb/image-libraryRun the install command to publish the migrations, config file, and service provider:
php artisan image-library:installThis will:
- Publish the configuration file to
config/image-library.php - Copy and register the
ImageLibraryServiceProviderin your application - Publish the database migrations
- Optionally run the migrations
SourceImages are the original images uploaded to the system. They are not directly linked to any model. They are meant for internal use in this package.
Images are the link between a SourceImage and your Model(s). The Image has a BelongsTo relationship to the SourceImage and a MorphTo relationship to your Model.
You can see these as an instance of the uploaded images in a specific use case. You can define the use case using the context attribute on the Image model.
ImageContexts allow you to define a configuration for images used in a specific way. Examples include "profile_picture", "thumbnail", "gallery_entry", "hero", etc.
They are fully customizable and should be defined in the ImageServiceProvider or in a custom service provider.
Images get generated based on the defined ImageContext when the Image model gets created or updated. This is based on the image_context_hash that is stored per Image. It is a hashed version of the whole configuration so that changes in the context will trigger regeneration of the images.
This package also generates WebP versions of images for better performance in modern browsers. You can configure whether to generate WebP versions globally in the config file or per ImageContext.
The package supports generating multiple responsive versions of images based on defined breakpoints. You can configure the sizes and aspect ratios for each breakpoint in the ImageContext, ensuring optimal display across different devices.
Each breakpoint can have a minimum and maximum width defined in the context. This allows the package to generate only necessary image sizes based on your design requirements.
Breakpoints define responsive screen sizes for image optimization. The package uses a Breakpoint enum that follows Tailwind CSS conventions, allowing you to specify different image configurations for various screen sizes.
Available breakpoints:
Breakpoint::Small('sm'): 640px and up - Mobile devices in landscape, small tabletsBreakpoint::Medium('md'): 768px and up - Tablets in portrait modeBreakpoint::Large('lg'): 1024px and up - Tablets in landscape, small desktopsBreakpoint::ExtraLarge('xl'): 1280px and up - Desktop screensBreakpoint::DoubleExtraLarge('2xl'): 1536px and up - Large desktop screens
You can use these breakpoints to define different aspect ratios, sizes, crop positions, and effects for different screen sizes, ensuring optimal image display across all devices.
Note: If the default breakpoints don't match your design system, you can create custom breakpoints. See Custom Breakpoints in the Configuration section.
If you don't need responsive images, you can disable breakpoints globally in the config file or per ImageContext.
The config file allows you to customize various aspects of the image library. Some key configuration options include:
defaults.disk: The default filesystem disk for storing images if not specified during uploaduse_breakpoints: Enable or disable responsive breakpoints globallygenerate.webp: Automatically generate WebP versions of images if not specified in the image contextgenerate.responsive_versions: Generate multiple sizes for responsive images if not specified in the image contextdefaults.crop_position: Default crop position for image transformations if not specified in the image contextmodels: Customize the Eloquent models used by the package to easily extend functionalityenums: Customize the enums used by the package to easily extend functionalityspatie_image.driver: Choose between 'gd' or 'imagick' for image manipulations
The package includes a JavaScript component that automatically sets the sizes attribute on picture elements rendered by the package. This ensures that the browser selects the most appropriate image size based on the actual display size of the image.
To include the script, add the following Blade component to the <head> section of your layout:
<x-image-library::scripts />ImageContexts are defined in your application's ImageLibraryServiceProvider that gets published during installation. This provider extends the base service provider and allows you to define contexts in the imageContexts() method:
<?php
namespace App\Providers;
use Outerweb\ImageLibrary\Entities\AspectRatio;
use Outerweb\ImageLibrary\Entities\ImageContext;
use Outerweb\ImageLibrary\Enums\Breakpoint;
use Outerweb\ImageLibrary\Providers\ImageLibraryServiceProvider as BaseServiceProvider;
use Override;
class ImageLibraryServiceProvider extends BaseServiceProvider
{
#[Override]
public function imageContexts(): array
{
return [
// Profile picture - square aspect ratio, single image
ImageContext::make('profile_picture')
->label(fn (): string => __('Profile Picture'))
->aspectRatio(AspectRatio::make(1, 1))
->allowsMultiple(false),
// Gallery images - 16:9 aspect ratio, multiple images allowed
ImageContext::make('gallery')
->label(fn (): string => __('Gallery'))
->aspectRatio(AspectRatio::make(16, 9))
->allowsMultiple(true),
// Thumbnail - square with responsive sizing
ImageContext::make('thumbnail')
->label(fn (): string => __('Thumbnail'))
->aspectRatio(AspectRatio::make(1, 1))
->maxWidth([
Breakpoint::Small->value => 150,
Breakpoint::Medium->value => 200,
Breakpoint::Large->value => 250,
])
->allowsMultiple(false),
// Image that does not use breakpoints
ImageContext::make('no_breakpoints')
->label(fn (): string => __('No Breakpoints'))
->useBreakpoints(false)
];
}
}ImageContexts provide extensive configuration options for different responsive breakpoints and image processing needs:
You can define a human-readable label for each context to use in your UI.
ImageContext::make('thumbnail')
->label('Thumbnail')
If you need localization, you can define the label using a closure:
ImageContext::make('thumbnail')
->label(fn() => __('Thumbnail'))If you need information about the ImageContext in the label, you can use the provided ImageContext instance:
ImageContext::make('thumbnail')
->label(fn(ImageContext $context) => __('Image Context: :context', ['context' => $context->key]))You can specify whether multiple images are allowed in this context:
ImageContext::make('gallery')
->allowsMultiple(true);By default, WebP versions are generated based on the global config. You can override this per context:
ImageContext::make('thumbnail')
->generateWebP(false);By default, breakpoints are used based on the global config. You can override this per context:
ImageContext::make('thumbnail')
->useBreakpoints(false);
⚠️ Caution: Make sure to call->useBreakpoints(false)before any other methods that depend on breakpoints, such asaspectRatio(),minWidth(),maxWidth(), etc. Otherwise, you may encounter errors since those methods check if breakpoints are enabled or not.
By default, responsive versions are generated based on the global config. You can override this per context:
ImageContext::make('thumbnail')
->generateResponsiveVersions(false);Note: If you disable breakpoints for an ImageContext, responsive versions will also be disabled as these are only generated when breakpoints are used.
The aspect ratio can be configured per Breakpoint in one of the following ways:
// Single aspect ratio for all breakpoints
ImageContext::make('thumbnail')
->aspectRatio(AspectRatio::make(1, 1));
// Different aspect ratios per breakpoint
ImageContext::make('thumbnail')
->aspectRatio([
Breakpoint::Small->value => AspectRatio::make(1, 1),
Breakpoint::Medium->value => AspectRatio::make(4, 3),
Breakpoint::Large->value => AspectRatio::make(16, 9),
Breakpoint::ExtraLarge->value => AspectRatio::make(16, 9),
Breakpoint::DoubleExtraLarge->value => AspectRatio::make(2, 1),
]);
// Per breakpoint
ImageContext::make('thumbnail')
->aspectRatioForBreakpoint(Breakpoint::Small, AspectRatio::make(1, 1))
// From a Breakpoint and up
ImageContext::make('thumbnail')
->aspectRatioFromBreakpoint(Breakpoint::Medium, AspectRatio::make(16, 9))
// Up till a Breakpoint
ImageContext::make('thumbnail')
->aspectRatioUpToBreakpoint(Breakpoint::Large, AspectRatio::make(4, 3))
// Between two Breakpoints
ImageContext::make('thumbnail')
->aspectRatioBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, AspectRatio::make(1, 1));You can define the minimum width of the image used in your design per Breakpoint in one of the following ways:
// Single minimum width for all breakpoints
ImageContext::make('thumbnail')
->minWidth(150)
// Different minimum widths per breakpoint
ImageContext::make('thumbnail')
->minWidth([
Breakpoint::Small->value => 100,
Breakpoint::Medium->value => 150,
Breakpoint::Large->value => 200,
Breakpoint::ExtraLarge->value => 250,
Breakpoint::DoubleExtraLarge->value => 300,
]);
// Per breakpoint
ImageContext::make('thumbnail')
->minWidthForBreakpoint(Breakpoint::Small, 100);
// From a Breakpoint and up
ImageContext::make('thumbnail')
->minWidthFromBreakpoint(Breakpoint::Medium, 150);
// Up till a Breakpoint
ImageContext::make('thumbnail')
->minWidthUpToBreakpoint(Breakpoint::Large, 200);
// Between two Breakpoints
ImageContext::make('thumbnail')
->minWidthBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, 150);You can define the maximum width of the image used in your design per Breakpoint in one of the following ways:
// Single maximum width for all breakpoints
ImageContext::make('thumbnail')
->maxWidth(250);
// Different maximum widths per breakpoint
ImageContext::make('thumbnail')
->maxWidth([
Breakpoint::Small->value => 150,
Breakpoint::Medium->value => 200,
Breakpoint::Large->value => 250,
Breakpoint::ExtraLarge->value => 300,
Breakpoint::DoubleExtraLarge->value => 350,
]);
// Per breakpoint
ImageContext::make('thumbnail')
->maxWidthForBreakpoint(Breakpoint::Small, 150);
// From a Breakpoint and up
ImageContext::make('thumbnail')
->maxWidthFromBreakpoint(Breakpoint::Medium, 200);
// Up till a Breakpoint
ImageContext::make('thumbnail')
->maxWidthUpToBreakpoint(Breakpoint::Large, 250);
// Between two Breakpoints
ImageContext::make('thumbnail')
->maxWidthBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, 200);By default, the crop position from the config file is used. You can override this per context and per Breakpoint in one of the following ways:
// Single crop position for all breakpoints
ImageContext::make('thumbnail')
->cropPosition(CropPosition::Center);
// Different crop positions per breakpoint
ImageContext::make('thumbnail')
->cropPosition([
Breakpoint::Small->value => CropPosition::Top,
Breakpoint::Medium->value => CropPosition::Center,
Breakpoint::Large->value => CropPosition::Bottom,
Breakpoint::ExtraLarge->value => CropPosition::Center,
Breakpoint::DoubleExtraLarge->value => CropPosition::Center,
]);
// Per breakpoint
ImageContext::make('thumbnail')
->cropPositionForBreakpoint(Breakpoint::Small, CropPosition::Top);
// From a Breakpoint and up
ImageContext::make('thumbnail')
->cropPositionFromBreakpoint(Breakpoint::Medium, CropPosition::Center);
// Up till a Breakpoint
ImageContext::make('thumbnail')
->cropPositionUpToBreakpoint(Breakpoint::Large, CropPosition::Bottom);
// Between two Breakpoints
ImageContext::make('thumbnail')
->cropPositionBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, CropPosition::Center);You can apply a blur effect to images in this context per Breakpoint in one of the following ways:
// Single blur value for all breakpoints
ImageContext::make('thumbnail')
->blur(10);
// Different blur values per breakpoint
ImageContext::make('thumbnail')
->blur([
Breakpoint::Small->value => 5,
Breakpoint::Medium->value => 10,
Breakpoint::Large->value => 15,
Breakpoint::ExtraLarge->value => 20,
Breakpoint::DoubleExtraLarge->value => 25,
]);
// Per breakpoint
ImageContext::make('thumbnail')
->blurForBreakpoint(Breakpoint::Small, 5);
// From a Breakpoint and up
ImageContext::make('thumbnail')
->blurFromBreakpoint(Breakpoint::Medium, 10);
// Up till a Breakpoint
ImageContext::make('thumbnail')
->blurUpToBreakpoint(Breakpoint::Large, 15);
// Between two Breakpoints
ImageContext::make('thumbnail')
->blurBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, 10);You can apply a greyscale effect to images in this context per Breakpoint in one of the following ways:
// Single greyscale value for all breakpoints
ImageContext::make('thumbnail')
->greyscale(true); // or even ->grayscale(true)
// Different greyscale values per breakpoint
ImageContext::make('thumbnail')
->greyscale([
Breakpoint::Small->value => false,
Breakpoint::Medium->value => true,
Breakpoint::Large->value => false,
Breakpoint::ExtraLarge->value => true,
Breakpoint::DoubleExtraLarge->value => false,
]);
// Per breakpoint
ImageContext::make('thumbnail')
->greyscaleForBreakpoint(Breakpoint::Small, false); // or even ->grayscaleForBreakpoint(Breakpoint::Small, false);
// From a Breakpoint and up
ImageContext::make('thumbnail')
->greyscaleFromBreakpoint(Breakpoint::Medium, true); // or even ->grayscaleFromBreakpoint(Breakpoint::Medium, true);
// Up till a Breakpoint
ImageContext::make('thumbnail')
->greyscaleUpToBreakpoint(Breakpoint::Large, false); // or even ->grayscaleUpToBreakpoint(Breakpoint::Large, false);
// Between two Breakpoints
ImageContext::make('thumbnail')
->greyscaleBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, true); // or even ->grayscaleBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, true);You can apply a sepia effect to images in this context per Breakpoint in one of the following ways:
// Single sepia value for all breakpoints
ImageContext::make('thumbnail')
->sepia(true);
// Different sepia values per breakpoint
ImageContext::make('thumbnail')
->sepia([
Breakpoint::Small->value => false,
Breakpoint::Medium->value => true,
Breakpoint::Large->value => false,
Breakpoint::ExtraLarge->value => true,
Breakpoint::DoubleExtraLarge->value => false,
]);
// Per breakpoint
ImageContext::make('thumbnail')
->sepiaForBreakpoint(Breakpoint::Small, false);
// From a Breakpoint and up
ImageContext::make('thumbnail')
->sepiaFromBreakpoint(Breakpoint::Medium, true);
// Up till a Breakpoint
ImageContext::make('thumbnail')
->sepiaUpToBreakpoint(Breakpoint::Large, false);
// Between two Breakpoints
ImageContext::make('thumbnail')
->sepiaBetweenBreakpoints(Breakpoint::Small, Breakpoint::Large, true);Add the HasImages trait to any Eloquent model that should support image attachments:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Outerweb\ImageLibrary\Traits\HasImages;
class Product extends Model
{
use HasImages;
// Your model code...
}The trait provides:
images(): Default polymorphic relationship returning all imagesattachImage(): Method to attach images with context validation- Automatic context validation and image replacement for single-image contexts
For more control over image relationships, you can define custom morphic relationships alongside the HasImages trait. This allows you to create type-specific relationships for different image contexts.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Outerweb\ImageLibrary\Models\Image;
use Outerweb\ImageLibrary\Traits\HasImages;
class Article extends Model
{
use HasImages;
/**
* Single featured image relationship
*/
public function featuredImage(): MorphOne
{
return $this->morphOne(Image::class, 'model')
->where('context', 'featured');
}
/**
* Multiple gallery images relationship
*/
public function galleryImages(): MorphMany
{
return $this->morphMany(Image::class, 'model')
->where('context', 'gallery');
}
/**
* Images for a specific layout block (useful for page builders)
*/
public function getLayoutBlockImages(int $blockId): MorphMany
{
return $this->images()
->whereJsonContains('custom_properties->layout_block_id', $blockId);
}
}If the default breakpoints don't match your design system, you can create a custom breakpoint enum. This is useful when you need different screen size thresholds or additional breakpoints.
First, create a custom enum that implements the ConfiguresBreakpoints contract:
<?php
namespace App\Enums;
use Illuminate\Support\Str;
use Outerweb\ImageLibrary\Contracts\ConfiguresBreakpoints;
enum CustomBreakpoint: string implements ConfiguresBreakpoints
{
case Mobile = 'mobile';
case Tablet = 'tablet';
case Desktop = 'desktop';
case UltraWide = 'ultrawide';
public static function sortedCases(): array
{
return collect(self::cases())
->sort(fn ($a, $b) => $a->getMinWidth() <=> $b->getMinWidth())
->all();
}
public function getLabel(): string
{
return match ($this) {
self::Mobile => 'Mobile',
self::Tablet => 'Tablet',
self::Desktop => 'Desktop',
self::UltraWide => 'Ultra Wide',
};
}
public function getMinWidth(): int
{
return match ($this) {
self::Mobile => 320,
self::Tablet => 768,
self::Desktop => 1200,
self::UltraWide => 1920,
};
}
public function getMaxWidth(): ?int
{
$index = array_search($this, self::sortedCases(), true);
$next = self::sortedCases()[$index + 1] ?? null;
return $next ? $next->getMinWidth() - 1 : null;
}
public function getSlug(): string
{
return Str::of($this->value)
->lower()
->slug()
->toString();
}
}Update your config/image-library.php file to use your custom enum:
'enums' => [
'breakpoint' => App\Enums\CustomBreakpoint::class,
],If you application or specific context does not require image versions per breakpoint, you can disable breakpoints:
'use_breakpoints' => false,ImageContext::make('thumbnail')
->useBreakpoints(false);
⚠️ Caution: Make sure to call->useBreakpoints(false)before any other methods that depend on breakpoints, such asaspectRatio(),minWidth(),maxWidth(), etc. Otherwise, you may encounter errors since those methods check if breakpoints are enabled or not.
Upload images from UploadedFile instances (typically from form submissions) to create SourceImage records:
use Outerweb\ImageLibrary\Facades\ImageLibrary;
// Basic upload using default settings
$sourceImage = ImageLibrary::upload($request->file('image'));
// The SourceImage is now stored and optimized, ready to be attached to models// Upload to specific disk
$sourceImage = ImageLibrary::upload($request->file('image'), [
'disk' => 's3',
]);
// Upload with custom properties and metadata
$sourceImage = ImageLibrary::upload($request->file('image'), [
'disk' => 's3',
'custom_properties' => [
'photographer' => 'John Doe',
'license' => 'Creative Commons',
'shoot_date' => '2024-01-15',
'camera_model' => 'Canon EOS R5'
],
]);- Automatic optimization: Images are processed using Spatie Image with your configured driver (GD/Imagick)
- Metadata extraction: Width, height, file size, and MIME type are automatically detected and stored
- UUID generation: A unique identifier is created for organized file storage
- File organization: Images are stored in a structured directory:
{base_path}/{uuid}/original.{extension} - Database record: A
SourceImagemodel is created with all metadata
After uploading a SourceImage, attach it to your models using the context system:
// Upload the source image
$sourceImage = ImageLibrary::upload($request->file('image'));
// Get your model
$product = Product::find(1);
// Attach with a context
$image = $product->attachImage($sourceImage, [
'context' => 'thumbnail'
]);
// The image is now attached and will be processed according to the context configuration// Attach with multilingual alt text
$image = $product->attachImage($sourceImage, [
'context' => 'featured_image',
'alt_text' => [
'en' => 'Taylor Otwell driving his lamborghini',
'nl' => 'Taylor Otwell rijdt in zijn lamborghini',
]
]);
// Attach with custom properties and metadata
$image = $product->attachImage($sourceImage, [
'context' => 'gallery',
'custom_properties' => [
'photographer' => 'Jane Smith',
'copyright' => '© 2024 Company Name',
'location' => 'Swiss Alps',
'camera_settings' => [
'aperture' => 'f/2.8',
'shutter_speed' => '1/500s',
'iso' => 200
]
],
'alt_text' => [
'en' => 'Mountain landscape photography'
]
]);When using custom relationships, you can still use the attachImage method. You can specify the relationship to use:
$image = $product->attachImage($sourceImage, [
'context' => 'featured'
], 'featuredImage');The attachImage method will replace the existing image if the context is not configured to allow multiple images. This ensures that single-image contexts always have only one associated image.
You can access your model's images through the images relationship or any custom relationships you've defined.
// Get all images
$images = $product->images;
// Query images by context
$thumbnails = $product->images()
->where('context', 'thumbnail')
->get();
// Query images with specific custom properties
$landscapeImages = $product->images()
->where('context', 'gallery')
->whereJsonContains('custom_properties->layout_builder_block_id', $blockId)
->get();You can render images in your views using the provided view component:
<x-image-library::image
:image="$image"
class="rounded-lg w-1/2"
/>This will render a picture element with the following:
- a
sourceelement perBreakpointwith responsive image urls - a
sourceelement perBreakpointfor the WebP versions of the responsive image urls - an
imgelement with the default image url, alt text, and any additional attributes you provide
Make sure you added the script component to the <head> of your layout:
<x-image-library::scripts />This script will set all sizes attributes of the picture elements automatically when:
- The page is loaded
- The viewport is resized
- The picture element is added to the viewport
- The picture element width changes
You can (re)generate images using the following artisan command:
php artisan image-library:generateThis will (re)generate all images files for all image records in the database based on their associated ImageContext configuration.
You can also (re)generate image files for a specific image:
php artisan image-library:generate {id}Or for multiple images:
php artisan image-library:generate {id1} {id2} {id3}This is a major version with breaking changes. See the Upgrade guide for detailed instructions.
Please see CHANGELOG for more information on what has changed recently.
The MIT License (MIT). Please see License File for more information.