shammaa / laravel-smart-glide
Advanced image processing toolkit for Laravel with unified /img routing, responsive components, and intelligent caching.
Requires
- php: ^8.3
- illuminate/cache: ^11.0|^12.0
- illuminate/filesystem: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/routing: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- illuminate/view: ^11.0|^12.0
- league/flysystem: ^3.0
- league/glide: ^3.0
Requires (Dev)
- illuminate/testing: ^11.0|^12.0
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^10.0
README
Advanced image processing toolkit for Laravel with unified /img routing, responsive components, intelligent caching, and a headless JSON API for React/Next.js/Vue frontends.
Features
- π Signed URLs & Validation β Protect image transformations with HMAC signatures, strict parameter validation, and extension whitelisting.
- πΌοΈ Responsive Components β Blade components generate
srcset,sizes, and background media queries automatically. - βοΈ Smart Profiles β Define reusable WebP/AVIF compression presets and override them from an external PHP profile file.
- π¦ Unified Delivery Path β All image responses flow through
/img/..., simplifying CDNs, caching rules, and observability. - π§ Intelligent Caching β LRU-inspired cache manifest with size budgets, warmup metadata, and background eviction.
- π§Ύ SEO-Ready β Configurable attributes (
loading,fetchpriority,aria,title, etc.) plus optional JSON-LDImageObjectsnippets. - π‘οΈ Security Rules β Block remote sources (optional), enforce max width/height/quality ranges, and stricter MIME filters.
- π§° DX Friendly β Publishable config, auto-discovered components, and clean service provider wiring.
- π Headless JSON API β
/img-dataendpoint returnssrc,srcset,sizes, andblurDataUrlas JSON for React/Vue/Mobile consumers. - π οΈ Admin Management API β HTTP endpoints to warm, forget, and inspect cached images directly from your dashboard.
- π Cache Introspection β Check image existence, read dimensions, and view cache statistics through the Facade or HTTP.
Requirements
- PHP 8.3+
- Laravel 11.x or 12.x
- GD or Imagick PHP extension
- League Glide 3.x (installed automatically)
Installation
composer require shammaa/laravel-smart-glide
The service provider and components are auto-discovered. If discovery is disabled, register the provider manually in bootstrap/app.php (Laravel 11+):
return Application::configure(basePath: dirname(__DIR__)) ->withProviders([ Shammaa\SmartGlide\SmartGlideServiceProvider::class, ]) ->create();
Publish the configuration file when you want to customize paths, caching, or SEO defaults:
php artisan vendor:publish --tag=smart-glide-config
This creates config/smart-glide.php in your application.
Environment Setup
- Signing keys
APP_KEY=base64:... # Optional dedicated key SMART_GLIDE_SIGNATURE_KEY=base64:...
After modifying the.envfile, always clear cached configuration:php artisan config:clear php artisan cache:clear
- Filesystem
- Originals default to
storage/app/public. - Processed images live in
storage/app/smart-glide-cache. - Run
php artisan storage:linkonce if your front-end needs to serve the originals.
- Originals default to
- Permissions Make sure the cache directory is writable by your web server user.
Quick Start
- Prepare assets
Place an image insidestorage/app/public(default source path). If you need public access to the originals, create the symlink once viaphp artisan storage:link. - Drop the component
<x-smart-glide-img src="team/photo.jpg" alt="Team portrait" width="960" height="640" />
- Load the page
The rendered<img>tag now contains a fully signedsrc, responsivesrcset,sizes, and fixed width/height for layout stability. All requests resolve to/img/team/photo.jpg?.... - Need a different source path?
Editconfig/smart-glide.php('source' => ...) and clear configuration cache:php artisan config:clear php artisan cache:clear
Verify in Tinker
php artisan tinker >>> app(Shammaa\SmartGlide\Support\SmartGlideManager::class)->deliveryUrl('team/photo.jpg', ['w' => 640])
Opening the returned URL in a browser should yield the processed image (HTTP 200). A 403 usually indicates an invalid or missing signatureβdoubleβcheck your APP_KEY/SMART_GLIDE_SIGNATURE_KEY and clear cached config.
Facade Usage
Resolve URLs anywhere in your Laravel app through the SmartGlide facade. This is convenient for tables, JSON APIs, or Livewire components where you need pre-signed image URLs without rendering Blade components.
use Shammaa\SmartGlide\Facades\SmartGlide; $url = SmartGlide::croppedUrl( path: 'team/photo.jpg', width: 160, height: 160, parameters: [ 'profile' => 'thumbnail', // merges preset params (quality, format, etc.) 'focus' => 'faces', // any additional Glide parameters ], );
In a DataTable JSON feed:
return TeamMember::query() ->select(['id', 'name', 'photo_path']) ->get() ->map(fn ($member) => [ 'id' => $member->id, 'name' => $member->name, 'photo' => SmartGlide::croppedUrl($member->photo_path, 96, 96, [ 'profile' => 'thumbnail', ]), ]);
Then render it in JavaScript:
$('#team-table').DataTable({ columns: [ { data: 'name' }, { data: 'photo', render: (data, type, row) => type === 'display' ? `<img src="${data}" alt="${row.name}" class="h-12 w-12 rounded-full object-cover" />` : data, orderable: false, searchable: false, }, ], });
Headless & API Usage (React / Vue / Mobile)
JSON Data API
Smart Glide provides a dedicated JSON endpoint at /img-data/{path} for headless frontends. This returns all responsive data as a structured JSON payload β ideal for React, Vue, Next.js, or mobile apps.
GET /img-data/products/phone.jpg?profile=hero&responsive=retina&blur_placeholder=1&schema=1
Response:
{
"src": "/img/products/phone.jpg?profile=hero&s=...",
"srcset": "/img/products/phone.jpg?w=640&... 640w, /img/products/phone.jpg?w=960&... 960w, ...",
"sizes": "(max-width: 640px) 100vw, (max-width: 960px) 100vw, ...",
"widths": [640, 960, 1280, 1920, 2560],
"blurDataUrl": "data:image/webp;base64,UklGR...",
"schema": {
"@context": "https://schema.org",
"@type": "ImageObject",
"contentUrl": "https://example.com/img/products/phone.jpg?..."
}
}
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
profile |
string | Named compression profile (hero, thumbnail, etc.) |
responsive |
string | Named responsive set, comma-separated widths, or 0 to disable |
blur_placeholder |
boolean | Include base64 LQIP data URI |
schema |
boolean | Include JSON-LD ImageObject data |
alt |
string | Alt text used in the schema caption |
w, h, fit, focus, q, fm |
mixed | Any additional Glide transformation parameters |
Facade: responsiveData()
Extract responsive data as a plain array from PHP:
use Shammaa\SmartGlide\Facades\SmartGlide; // In a Laravel Controller or Resource $imageData = SmartGlide::responsiveData( path: 'products/phone.jpg', profile: 'thumbnail', responsive: 'retina' // or [320, 640, 960] ); return response()->json([ 'product_title' => 'Awesome Phone', 'image' => $imageData ]);
Facade: apiPayload()
Build the complete JSON-ready payload (including blur placeholder and dimensions) from the Facade β perfect for Inertia.js props and Laravel API Resources:
use Shammaa\SmartGlide\Facades\SmartGlide; // In a Laravel Resource or Inertia page return SmartGlide::apiPayload('products/phone.jpg', [ 'profile' => 'hero', 'responsive' => 'retina', 'blur_placeholder' => true, 'dimensions' => true, ]); // Returns: { src, srcset, sizes, widths, blurDataUrl, dimensions: { width, height } }
Consumption in React/Vue
Simply bind the returned attributes to your <img> tag:
// React / Vue Example <img src={image.src} srcSet={image.srcset} sizes={image.sizes} alt={title} loading="lazy" />
For a full-featured React/Next.js SDK, install the companion package
smart-glide-react(npm install smart-glide-react) which provides<SmartGlideImage>, hooks, and Next.js loader integration.
Image Introspection
Check Image Existence
if (SmartGlide::imageExists('avatars/user-42.jpg')) { // Image found in source directory }
Get Original Dimensions
$dims = SmartGlide::dimensions('hero.jpg'); // ['width' => 3840, 'height' => 2160]
Generate Multiple URLs
$urls = SmartGlide::multipleUrls('hero.jpg', [640, 960, 1280], ['profile' => 'hero']); // [640 => '/img/hero.jpg?w=640&...', 960 => '/img/hero.jpg?w=960&...', ...]
Admin Management API
Enable HTTP endpoints for managing the image cache directly from your website or admin dashboard.
Setup
SMART_GLIDE_ADMIN_ENABLED=true SMART_GLIDE_ADMIN_PATH=/img-admin
Security: Admin routes are protected by
authmiddleware by default. Configure this inconfig/smart-glide.phpunderadmin.middleware.
Endpoints
Cache Statistics
GET /img-admin/stats
{
"count": 152,
"size_mb": 48.3,
"cache_path": "/var/www/storage/app/smart-glide-cache",
"source_path": "/var/www/storage/app/public",
"max_size_mb": 1024
}
Full Manifest
GET /img-admin/stats/manifest
Returns every cached rendition with path, width, profile, format, and timestamps.
Warm a Single Image
POST /img-admin/warm
Content-Type: application/json
{
"path": "products/phone.jpg",
"profile": "hero",
"widths": [640, 960, 1280]
}
{ "warmed": true, "path": "products/phone.jpg", "widths": [640, 960, 1280] }
Warm All Source Images
POST /img-admin/warm-all
Content-Type: application/json
{
"profile": "thumbnail",
"widths": [320, 640],
"extensions": ["jpg", "png"]
}
{ "warmed_count": 48, "skipped_count": 3, "size_mb": 52.1, "total_entries": 192 }
Evict Cache for an Image
Call this after replacing or updating the original file:
POST /img-admin/forget
Content-Type: application/json
{ "path": "products/phone.jpg" }
{ "forgotten": true, "path": "products/phone.jpg", "deleted_count": 5 }
Check Image Existence
GET /img-admin/exists?path=products/phone.jpg
{ "exists": true, "path": "products/phone.jpg" }
Get Image Dimensions
GET /img-admin/dimensions?path=products/phone.jpg
{ "path": "products/phone.jpg", "width": 3840, "height": 2160 }
Cache Management (PHP)
Warm Paths Programmatically
Pre-process and cache image renditions (ideal for queued jobs):
// Warm specific widths SmartGlide::warmPath('products/phone.jpg', [320, 640, 960, 1280], ['profile' => 'hero']); // Warm using config breakpoints SmartGlide::warmPath('hero.jpg');
Forget (Purge) a Path
Delete all cached renditions after replacing the original file:
$deleted = SmartGlide::forgetPath('products/phone.jpg'); // Returns count of deleted entries (e.g. 5)
Cache Statistics
$stats = SmartGlide::cacheStats(); // ['count' => 152, 'size_mb' => 48.3, 'manifest' => [...]]
Configuration Overview
The config file (config/smart-glide.php) exposes several groups:
| Key | Description |
|---|---|
source, cache, delivery_path |
Control filesystem paths and the unified /img endpoint. |
security |
Toggle URL signing, allowed formats, max dimensions, quality ranges, and remote sources. |
profiles & profile_file |
Define reusable transformation presets; merge an external PHP file at runtime. |
breakpoints |
Global widths for automatic srcset generation. |
responsive_sets |
Named breakpoint aliases usable from the components. |
cache_strategy |
Enforce cache size (MB) and LRU time window. |
cache_headers |
Configure browser cache days and enable ETag responses. |
logging |
Enable processing logs and choose the log channel. |
seo |
Default attributes for <img> / background components and structured data behaviour. |
api |
Enable/disable the headless JSON API endpoint and its middleware. |
admin |
Enable/disable admin management endpoints, set path and auth middleware. |
Tip: set
SMART_GLIDE_SCHEMA_ENABLED=trueto emit JSON-LDImageObjectsnippets for every component by default.
Default Profiles & Responsive Sets
Profiles (config('smart-glide.profiles'))
| Name | Description |
|---|---|
default |
Outputs WebP at quality 82. |
thumbnail |
320Γ320 crop for avatars or cards. |
hero |
1600Γ900 centered crop for hero banners. |
portrait |
800Γ1200 crop prioritising faces. |
square |
600Γ600 crop for grids. |
profile_photo |
400Γ400 crop focused on faces (avatars). |
cover |
2048Γ1152 crop suitable for cover art. |
background |
2560Γ1440 max-fit background-friendly rendition. |
Use them via profile="hero" or override/add more in your config file.
Responsive sets (config('smart-glide.responsive_sets'))
| Name | Widths |
|---|---|
hero |
640, 960, 1280, 1600, 1920 |
thumbnails |
240, 320, 480 |
square |
320, 480, 640 |
portrait |
480, 768, 1024 |
hd |
960, 1280, 1600 |
fhd |
1280, 1600, 1920, 2560 |
retina |
640, 960, 1280, 1920, 2560 |
Reference them with responsive="retina" or specify an inline array.
Blade Components
<x-smart-glide-img>
Render responsive <img> tags with minimal boilerplate.
<x-smart-glide-img src="portfolio/hero.jpg" profile="hero" alt="Product hero image" class="rounded-xl shadow-lg" style="display:block" aspect-ratio="16:9" :params="['fit' => 'crop', 'focus' => 'center']" :seo="[ 'fetchpriority' => 'high', 'title' => 'Hero Image', 'data-license' => 'CC-BY-4.0', ]" schema />
Common properties
| Prop | Type | Description |
|---|---|---|
src |
string | Relative path (inside smart-glide.source). |
profile |
string | Apply a config profile (profiles.hero). |
params |
array | Additional Glide parameters (width, height, etc.). |
style |
string | Inline styles applied to <img>. |
aspect-ratio |
string | Aspect ratio hint (e.g. 16:9, 1.5). |
width / height |
int | Native dimension attributes for layout stability. |
seo |
array | Extra HTML attributes (fetchpriority, title, itemprop, β¦). |
schema |
bool | Force JSON-LD emission for this image only. |
Structured data respects component-level overrides and merges extra fields from config('smart-glide.seo.structured_data.fields').
View Overrides & Fallback Rendering
When you publish the package views, make sure resources/views/vendor/smart-glide/components/img.blade.php is kept in sync with the version shipped by the package. If it is removed or left empty, the component now falls back to rendering the <img> tag inline, so URLs remain signed even with an incomplete override. You can safely delete the override to restore the default template.
How srcset Is Generated
- Smart Glide reads breakpoint widths from
config('smart-glide.breakpoints')by default. - For each width, the component clones your parameters, overrides
w, and calls the delivery URL to produce entries like/img/photo.jpg?w=640&... 640w. - The generated HTML will look similar to:
<img src="/img/photo.jpg?w=960&fm=webp&q=82&s=..." srcset="/img/photo.jpg?w=360&... 360w, /img/photo.jpg?w=640&... 640w, ..." sizes="(max-width: 360px) 100vw, (max-width: 640px) 100vw, ..." >
- Update the breakpoints globally by editing
config('smart-glide.breakpoints'), or define named presets inresponsive_setsfor reuse (responsive="hero"). - Override widths without touching the config:
<x-smart-glide-img src="gallery/piece.jpg" :responsive="[320, 640, 960]" />
- Use a named preset from
config('smart-glide.responsive_sets'):<x-smart-glide-img src="hero.jpg" responsive="hero" />
- Disable
srcsetentirely when you want a single rendition:<x-smart-glide-img src="logo.png" :responsive="false" />
- If you need full control, set the attributes manually via
:seo="['sizes' => '...']"or['srcset' => '...'].
<x-smart-glide-bg>
Responsive background helper with media queries and placeholders.
<x-smart-glide-bg src="banners/summit.jpg" profile="hero" class="hero-banner" alt="Annual summit banner" lazy="true" :seo="['data-license' => 'Internal use only']" responsive="hero" />
The component outputs a wrapper <div> with inline styles plus an optional <style> block for breakpoint-specific backgrounds. A blurred placeholder overlay is included when lazy is true.
<x-smart-glide-picture>
Responsive <picture> helper that lets you define multiple <source> breakpoints and a Smart Glide powered fallback.
<x-smart-glide-picture src="gallery/feature.jpg" alt="Feature image" class="feature-picture" aspect-ratio="1:1" :sources="[ ['media' => '(min-width: 1200px)', 'widths' => [1200], 'params' => ['fit' => 'crop', 'w' => 1200, 'h' => 675]], ['media' => '(min-width: 768px)', 'widths' => [900], 'params' => ['w' => 900, 'h' => 506]], ]" />
Each source entry accepts:
| Key | Description |
|---|---|
media |
Optional media query for the <source> tag. |
src |
Override image path for this source (defaults to component src). |
params |
Glide parameters merged with the base parameters. |
profile |
Profile name to merge with parameters. |
widths / responsive |
Array or preset used to generate the srcset. |
sizes |
Custom sizes attribute. |
type |
MIME type hint for the source. |
srcset |
Custom array of descriptors if you need full control. |
img-class / img-style |
Styling for the fallback <img>. |
img-width / img-height |
Native width/height attributes on fallback <img>. |
width / height |
Set attributes on the wrapping <picture>. |
aspect-ratio |
Sets CSS aspect-ratio on fallback <img>. |
The internal <img> fallback inherits Smart Glide features (SEO attributes, structured data, signed URLs) and can be styled separately via img-class.
Picture Presets
Skip repeating :sources="[]" arrays in every template by defining presets in config('smart-glide.picture_presets'):
'picture_presets' => [ 'feature' => [ ['media' => '(min-width: 1200px)', 'widths' => [1200], 'params' => ['fit' => 'crop', 'w' => 1200, 'h' => 675]], ['media' => '(min-width: 768px)', 'widths' => [900], 'params' => ['fit' => 'crop', 'w' => 900, 'h' => 506]], ], ],
Use the preset in Blade:
<x-smart-glide-picture preset="feature" src="{{ $article->featured_image }}" alt="{{ $article->title }}" aspect-ratio="16:9" />
You can still pass :sources="[]" along with a preset; they will be appended for page-specific overrides.
Signing & Security
- URLs are signed with HMAC using
SMART_GLIDE_SIGNATURE_KEYorAPP_KEYwhensecurity.secure_urlsis enabled. - Parameters are sanitized and validated against maximum width/height and quality limits.
- Remote image sources are rejected by default; enable them per environment with
SMART_GLIDE_ALLOW_REMOTE=true. - Allowed output formats (
fm) and file extensions are configurable to avoid unsafe file types.
Requests with invalid signatures or disallowed parameters return 403/400 responses automatically.
Smart Cache
Smart Glide stores processed images in the configured cache path and tracks metadata in a manifest stored in your cache store.
max_size_mblimits total cache footprint.lru_windowdetermines how far back "least recently used" entries remain before eviction.- Metadata includes
cached_at,last_accessed, andcache_filereferences to support background cleanup.
Responses include browser cache headers (Cache-Control, Expires) and optional ETag. HTTP clients respecting If-None-Match will receive 304 Not Modified when appropriate.
External Profiles
To allow non-developers to tweak compression parameters, create a PHP file that returns an array:
<?php return [ 'hero' => ['w' => 1920, 'h' => 1080, 'fm' => 'webp', 'q' => 80], 'thumbnail' => ['w' => 320, 'h' => 320, 'fit' => 'crop', 'q' => 72], ];
Point SMART_GLIDE_PROFILE_FILE to this file. Its definitions merge on top of the inline profiles config.
CLI Helpers
# Clear all cached renditions php artisan smart-glide:clear-cache --force # Pre-warm a single image php artisan smart-glide:warm products/phone.jpg --profile=hero --widths=640,960,1280 # Pre-warm all source images php artisan smart-glide:warm --all --ext=jpg,png # View cache statistics php artisan smart-glide:stats # View full manifest php artisan smart-glide:stats --manifest
Scheduled Cache Purge
The package ships with a built-in Artisan command and scheduler hook:
- Configure the daily purge time via
SMART_GLIDE_CACHE_PURGE_TIME(default03:00). - Smart Glide registers
smart-glide:clear-cacheautomatically; when the scheduler is enabled (php artisan schedule:run), cached renditions are cleared daily at the configured time. - Run manually with
php artisan smart-glide:clear-cache --force.
Testing
composer test # PHPUnit (requires Laravel testing harness) composer test-coverage # Coverage (if configured) composer analyse # Static analysis (PHPStan/Psalm if added)
Because this is a Laravel package, use Orchestra Testbench for isolated package tests.
Companion: React / Next.js SDK
For React and Next.js projects, install the companion package smart-glide-react:
npm install smart-glide-react
It provides:
<SmartGlideImage>β Full-featured<img>component with blur placeholders, priority loading, and JSON-LD<SmartGlidePicture>β<picture>element with multiple<source>branches<SmartGlideBackground>β Responsive CSS background containeruseSmartGlide()β Hook that fetches image data from the/img-dataAPIsmartGlideNextLoaderβ Custom loader fornext/imagefetchSmartGlideData()β Server-side fetcher for Next.js App Router (ISR support)
See the smart-glide-react README for full documentation.
Troubleshooting
| Symptom | Likely Cause | Resolution |
|---|---|---|
403 Smart Glide signature missing/invalid |
APP_KEY or SMART_GLIDE_SIGNATURE_KEY changed, or URL copied without the signed query string. |
Restore the original key, run php artisan config:clear & php artisan cache:clear, and always use URLs generated by Smart Glide. |
Image returns 404 |
Asset not found inside config('smart-glide.source'). |
Move the file into the source directory (default storage/app/public) or update the config accordingly. |
| Links must be absolute | APP_URL missing or incorrect. |
Set APP_URL in .env; deliveryUrl() honours it when producing URLs. |
| Cached variant stale | Old rendition remains in storage/app/smart-glide-cache. |
Use SmartGlide::forgetPath('path') or call POST /img-admin/forget to purge. |
| Admin routes return 404 | Admin API not enabled. | Set SMART_GLIDE_ADMIN_ENABLED=true in .env and clear config cache. |
License
Released under the MIT License.