'contexts' => [
// User posts - separate per user and post
'post' => 'user-posts/{user_id}/{post_id}',
// Gallery - separate per user and album
'gallery' => 'galleries/{user_id}/{album_id}',
// Avatars - per user only
'avatar' => 'avatars/{user_id}',
// Fanpage content
'fanpage' => 'fanpages/{fanpage_id}/{type}',
// Your custom contexts
'product' => 'products/{category_id}/{product_id}',
'team' => 'companies/{company_id}/team',
],
use Moonlight\Thumbnails\Traits\HasThumbnails;
class UserPost extends Model
{
use HasThumbnails;
// Define default context for this model
protected $thumbnailContext = 'post';
// Provide context data automatically
public function getThumbnailContextData(): array
{
return [
'user_id' => $this->user_id,
'post_id' => $this->id,
];
}
}
// In Blade - context applied automatically!
<img src="{{ $post->thumbnail('image.jpg', 'small') }}">
{{-- Auto-uses 'post' context with user_id and post_id --}}
// Return ONLY path, React will build thumbnail URL
public function getMediaFilesAttribute(): array
{
$mediaFiles = [];
foreach ($this->attachments as $attachment) {
if ($attachment['type'] === 'image') {
$mediaFiles[] = [
'type' => $attachment['type'],
'path' => $attachment['path'], // e.g., 'user-posts/1/12/img.jpg'
'url' => asset('storage/' . $attachment['path']),
'alt' => $attachment['original_name'],
];
}
}
return $mediaFiles;
}
// config/thumbnails.php
'method' => 'resize',
// config/thumbnails.php
'method' => 'crop',
// config/thumbnails.php
'method' => 'fit',
use Moonlight\Thumbnails\Facades\Thumbnail;
$url = Thumbnail::thumbnail('photos/image.jpg', 'medium');
use Moonlight\Thumbnails\Services\ThumbnailService;
class ImageController
{
public function show(ThumbnailService $thumbnails)
{
$url = $thumbnails->thumbnail('photos/image.jpg', 'medium');
}
}
'driver' => 'gd', // 'gd' (default), 'imagick', or 'intervention'
'driver' => 'gd',
'driver' => 'imagick',
'driver' => 'intervention',
'quality' => 85, // JPEG quality (1-100)
'fallback_on_error' => true, // Return original on error
'cache_control' => 'public, max-age=31536000', // 1 year cache
use Moonlight\Thumbnails\Traits\HasThumbnails;
class UserPost extends Model
{
use HasThumbnails;
// Define which fields contain images
protected $thumbnailFields = ['cover_image', 'gallery_image'];
}
// Usage in model
$post->thumbnail('cover_image', 'small'); // Get thumbnail URL
$post->thumbnails('cover_image'); // Get all sizes: ['small' => 'url', ...]
use Moonlight\Thumbnails\Facades\Thumbnail;
// Delete all thumbnails for an image
Thumbnail::deleteThumbnails('photos/image.jpg');
// Clear all thumbnails in directory
Thumbnail::clearAllThumbnails('photos');
// Clear ALL thumbnails in app
Thumbnail::clearAllThumbnails();
// config/thumbnails.php
'smart_crop' => [
'enabled' => true,
'algorithm' => 'energy', // 'energy', 'faces', 'saliency'
'rule_of_thirds' => true, // Align focal point to rule of thirds
],
thumbnail_extension('photos/photo.jpg'); // "webp" with THUMBNAILS_FORMAT=webp, "jpg" by default
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class StorageController extends Controller
{
/**
* Serve files from storage with signed URL validation
*
* Automatically routes audio files to AudioStreamController for
* HTTP Range Request support (seeking in audio players).
*/
public function serve(Request $request, string $path)
{
$path = urldecode($path);
// Optional: Delegate audio files to AudioStreamController
// if (preg_match('/\.(mp3|wav|ogg|m4a|flac)$/i', $path)) {
// return app(AudioStreamController::class)->stream($request, $path);
// }
$disk = config('thumbnails.disk', 'public');
$fullPath = Storage::disk($disk)->path($path);
if (!file_exists($fullPath)) {
abort(404, 'File not found');
}
// Set cache headers based on signed URLs config
$cacheControl = config('thumbnails.signed_urls.enabled')
? 'private, no-cache, must-revalidate'
: 'public, max-age=31536000';
return response()->file($fullPath, [
'Cache-Control' => $cacheControl,
'Content-Type' => mime_content_type($fullPath) ?: 'application/octet-stream',
]);
}
}
use App\Http\Controllers\StorageController;
Route::get('/storage/{path}', [StorageController::class, 'serve'])
->where('path', '.*')
->middleware('signed') // Laravel's native signed middleware
->name('storage.serve');