{"owner":"spatie","repo":"laravel-medialibrary","hasSkills":true,"totalSkillsCount":2,"totalTokensCount":4444,"categories":["anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["resources/boost/skills/medialibrary-development/SKILL.md","resources/boost/skills/medialibrary-development/references/medialibrary-guide.md"],"skills":{"resources/boost/skills/medialibrary-development/SKILL.md":"---\nname: medialibrary-development\ndescription: Build and work with spatie/laravel-medialibrary features including associating files with Eloquent models, defining media collections and conversions, generating responsive images, and retrieving media URLs and paths.\nlicense: MIT\nmetadata:\n  author: Spatie\n---\n\n# Media Library Development\n\n## Overview\nUse spatie/laravel-medialibrary to associate files with Eloquent models. Supports image/video conversions, responsive images, multiple collections, and various storage disks.\n\n## When to Activate\n- Activate when working with file uploads, media attachments, or image processing in Laravel.\n- Activate when code references `HasMedia`, `InteractsWithMedia`, the `Media` model, or media collections/conversions.\n- Activate when the user wants to add, retrieve, convert, or manage files attached to Eloquent models.\n\n## Scope\n- In scope: media uploads, collections, conversions, responsive images, custom properties, file retrieval, path/URL generation.\n- Out of scope: general file storage without Eloquent association, non-Laravel frameworks.\n\n## Workflow\n1. Identify the task (model setup, adding media, defining conversions, retrieving files, etc.).\n2. Read `references/medialibrary-guide.md` and focus on the relevant section.\n3. Apply the patterns from the reference, keeping code minimal and Laravel-native.\n\n## Core Concepts\n\n### Model Setup\nEvery model that should have media must implement `HasMedia` and use the `InteractsWithMedia` trait:\n\n```php\nuse Spatie\\MediaLibrary\\HasMedia;\nuse Spatie\\MediaLibrary\\InteractsWithMedia;\n\nclass BlogPost extends Model implements HasMedia\n{\n    use InteractsWithMedia;\n}\n```\n\n### Adding Media\n```php\n$blogPost->addMedia($file)->toMediaCollection('images');\n$blogPost->addMediaFromUrl($url)->toMediaCollection('images');\n$blogPost->addMediaFromRequest('file')->toMediaCollection('images');\n```\n\n### Defining Collections\n```php\npublic function registerMediaCollections(): void\n{\n    $this->addMediaCollection('avatar')->singleFile();\n    $this->addMediaCollection('downloads')->useDisk('s3');\n}\n```\n\n### Defining Conversions\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media;\nuse Spatie\\Image\\Enums\\Fit;\n\npublic function registerMediaConversions(?Media $media = null): void\n{\n    $this->addMediaConversion('thumb')\n        ->fit(Fit::Contain, 300, 300)\n        ->nonQueued();\n}\n```\n\n### Retrieving Media\n```php\n$url = $model->getFirstMediaUrl('images');\n$thumbUrl = $model->getFirstMediaUrl('images', 'thumb');\n$allMedia = $model->getMedia('images');\n```\n\n## Do and Don't\n\nDo:\n- Always implement the `HasMedia` interface alongside the `InteractsWithMedia` trait.\n- Use `?Media $media = null` as the parameter for `registerMediaConversions()`.\n- Call `->toMediaCollection()` to finalize adding media.\n- Use `->nonQueued()` for conversions that should run synchronously.\n- Use `->singleFile()` on collections that should only hold one file.\n- Use `Spatie\\Image\\Enums\\Fit` enum values for fit methods.\n\nDon't:\n- Don't forget to run `php artisan vendor:publish --provider=\"Spatie\\MediaLibrary\\MediaLibraryServiceProvider\" --tag=\"medialibrary-migrations\"` before migrating.\n- Don't use `env()` for disk configuration; use `config()` or set it in `config/media-library.php`.\n- Don't call `addMedia()` without calling `toMediaCollection()` — the media won't be saved.\n- Don't reference conversion names that aren't registered in `registerMediaConversions()`.\n\n## References\n- `references/medialibrary-guide.md`\n","resources/boost/skills/medialibrary-development/references/medialibrary-guide.md":"# Laravel Media Library Reference\n\nComplete reference for `spatie/laravel-medialibrary`. Full documentation: https://spatie.be/docs/laravel-medialibrary\n\n## Model Setup\n\nImplement `HasMedia` and use `InteractsWithMedia`:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Spatie\\MediaLibrary\\HasMedia;\nuse Spatie\\MediaLibrary\\InteractsWithMedia;\n\nclass BlogPost extends Model implements HasMedia\n{\n    use InteractsWithMedia;\n\n    public function registerMediaCollections(): void\n    {\n        $this->addMediaCollection('images');\n    }\n\n    public function registerMediaConversions(?Media $media = null): void\n    {\n        $this->addMediaConversion('thumb')\n            ->fit(Fit::Contain, 300, 300);\n    }\n}\n```\n\n## Adding Media\n\n### From uploaded file\n```php\n$model->addMedia($request->file('image'))->toMediaCollection('images');\n```\n\n### From request (shorthand)\n```php\n$model->addMediaFromRequest('image')->toMediaCollection('images');\n```\n\n### From URL\n```php\n$model->addMediaFromUrl('https://example.com/image.jpg')->toMediaCollection('images');\n```\n\n### From string content\n```php\n$model->addMediaFromString('raw content')->usingFileName('file.txt')->toMediaCollection('files');\n```\n\n### From base64\n```php\n$model->addMediaFromBase64($base64Data)->usingFileName('photo.jpg')->toMediaCollection('images');\n```\n\n### From stream\n```php\n$model->addMediaFromStream($stream)->usingFileName('file.pdf')->toMediaCollection('files');\n```\n\n### From existing disk\n```php\n$model->addMediaFromDisk('path/to/file.jpg', 's3')->toMediaCollection('images');\n```\n\n### Multiple files from request\n```php\n$model->addMultipleMediaFromRequest(['images'])->each(function ($fileAdder) {\n    $fileAdder->toMediaCollection('images');\n});\n\n$model->addAllMediaFromRequest()->each(function ($fileAdder) {\n    $fileAdder->toMediaCollection('images');\n});\n```\n\n### Copy instead of move\n```php\n$model->copyMedia($pathToFile)->toMediaCollection('images');\n// or\n$model->addMedia($pathToFile)->preservingOriginal()->toMediaCollection('images');\n```\n\n## FileAdder Options\n\nAll methods are chainable before calling `toMediaCollection()`:\n\n```php\n$model->addMedia($file)\n    ->usingName('Custom Name')              // display name\n    ->usingFileName('custom-name.jpg')      // filename on disk\n    ->setOrder(3)                           // order within collection\n    ->withCustomProperties(['alt' => 'A landscape photo'])\n    ->withManipulations(['thumb' => ['filter' => 'greyscale']])\n    ->withResponsiveImages()                // generate responsive variants\n    ->storingConversionsOnDisk('s3')         // put conversions on different disk\n    ->addCustomHeaders(['CacheControl' => 'max-age=31536000'])\n    ->toMediaCollection('images');\n```\n\n### Store on cloud disk\n```php\n$model->addMedia($file)->toMediaCollectionOnCloudDisk('images');\n```\n\n## Media Collections\n\nDefine in `registerMediaCollections()`:\n\n```php\npublic function registerMediaCollections(): void\n{\n    // Basic collection\n    $this->addMediaCollection('images');\n\n    // Single file (replacing previous on new upload)\n    $this->addMediaCollection('avatar')\n        ->singleFile();\n\n    // Keep only latest N items\n    $this->addMediaCollection('recent_photos')\n        ->onlyKeepLatest(5);\n\n    // Specific disk\n    $this->addMediaCollection('downloads')\n        ->useDisk('s3');\n\n    // With conversions disk\n    $this->addMediaCollection('photos')\n        ->useDisk('s3')\n        ->storeConversionsOnDisk('s3-thumbnails');\n\n    // MIME type restriction\n    $this->addMediaCollection('documents')\n        ->acceptsMimeTypes(['application/pdf', 'application/zip']);\n\n    // Custom validation\n    $this->addMediaCollection('images')\n        ->acceptsFile(function ($file) {\n            return $file->mimeType === 'image/jpeg';\n        });\n\n    // Fallback URL/path when collection is empty\n    $this->addMediaCollection('avatar')\n        ->singleFile()\n        ->useFallbackUrl('/images/default-avatar.jpg')\n        ->useFallbackPath(public_path('/images/default-avatar.jpg'));\n\n    // Enable responsive images for entire collection\n    $this->addMediaCollection('hero_images')\n        ->withResponsiveImages();\n\n    // Collection-specific conversions\n    $this->addMediaCollection('photos')\n        ->registerMediaConversions(function () {\n            $this->addMediaConversion('card')\n                ->fit(Fit::Crop, 400, 400);\n        });\n}\n```\n\n## Media Conversions\n\nDefine in `registerMediaConversions()`:\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media;\nuse Spatie\\Image\\Enums\\Fit;\n\npublic function registerMediaConversions(?Media $media = null): void\n{\n    $this->addMediaConversion('thumb')\n        ->fit(Fit::Contain, 300, 300)\n        ->nonQueued();\n\n    $this->addMediaConversion('preview')\n        ->fit(Fit::Crop, 500, 500)\n        ->withResponsiveImages()\n        ->queued();\n\n    $this->addMediaConversion('banner')\n        ->fit(Fit::Max, 1200, 630)\n        ->performOnCollections('images', 'headers')\n        ->nonQueued()\n        ->sharpen(10);\n\n    // Conditional conversion based on media properties\n    if ($media?->mime_type === 'image/png') {\n        $this->addMediaConversion('png-thumb')\n            ->fit(Fit::Contain, 150, 150);\n    }\n\n    // Keep original format instead of converting to jpg\n    $this->addMediaConversion('web')\n        ->fit(Fit::Max, 800, 800)\n        ->keepOriginalImageFormat();\n\n    // PDF page rendering\n    $this->addMediaConversion('pdf-preview')\n        ->pdfPageNumber(1)\n        ->fit(Fit::Contain, 400, 400);\n\n    // Video frame extraction\n    $this->addMediaConversion('video-thumb')\n        ->extractVideoFrameAtSecond(5)\n        ->fit(Fit::Crop, 300, 300);\n}\n```\n\n### Image Manipulation Methods (via spatie/image)\n\nResizing and fitting:\n- `width(int)`, `height(int)` — constrain dimensions\n- `fit(Fit, int, int)` — fit within bounds using `Fit::Contain`, `Fit::Max`, `Fit::Fill`, `Fit::Stretch`, `Fit::Crop`\n- `crop(int, int)` — crop to exact dimensions\n\nEffects:\n- `sharpen(int)`, `blur(int)`, `pixelate(int)`\n- `greyscale()`, `sepia()`\n- `brightness(int)`, `contrast(int)`, `colorize(int, int, int)`\n\nOrientation:\n- `orientation(int)`, `flip(string)`, `rotate(int)`\n\nFormat:\n- `format(string)` — `'jpg'`, `'png'`, `'webp'`, `'avif'`\n- `quality(int)` — 1-100\n\nOther:\n- `border(int, string, string)`, `watermark(string)`\n- `optimize()`, `nonOptimized()`\n\n### Conversion Configuration\n\n- `performOnCollections('col1', 'col2')` — limit to specific collections\n- `queued()` / `nonQueued()` — run async or sync\n- `withResponsiveImages()` — also generate responsive variants for this conversion\n- `keepOriginalImageFormat()` — preserve png/webp/gif instead of converting to jpg\n- `pdfPageNumber(int)` — which PDF page to render\n- `extractVideoFrameAtSecond(int)` — video thumbnail timing\n\n## Retrieving Media\n\n### Getting media items\n```php\n$media = $model->getMedia('images');                    // all in collection\n$first = $model->getFirstMedia('images');               // first item\n$last  = $model->getLastMedia('images');                // last item\n$has   = $model->hasMedia('images');                    // boolean check\n```\n\n### Getting URLs\n```php\n$url     = $model->getFirstMediaUrl('images');           // original URL\n$thumbUrl = $model->getFirstMediaUrl('images', 'thumb'); // conversion URL\n$lastUrl  = $model->getLastMediaUrl('images', 'thumb');\n```\n\n### Getting paths\n```php\n$path     = $model->getFirstMediaPath('images');\n$thumbPath = $model->getFirstMediaPath('images', 'thumb');\n```\n\n### Temporary URLs (S3)\n```php\n$tempUrl = $model->getFirstTemporaryUrl(\n    now()->addMinutes(30),\n    'images',\n    'thumb'\n);\n```\n\n### Fallback URLs\n```php\n$url = $model->getFallbackMediaUrl('avatar');\n```\n\n### From the Media model\n```php\n$media = $model->getFirstMedia('images');\n\n$media->getUrl();                    // original URL\n$media->getUrl('thumb');             // conversion URL\n$media->getPath();                   // disk path\n$media->getFullUrl();                // full URL with domain\n$media->getTemporaryUrl(now()->addMinutes(30));\n$media->hasGeneratedConversion('thumb');  // check if conversion exists\n```\n\n### Filtering media\n```php\n$media = $model->getMedia('images', function (Media $media) {\n    return $media->getCustomProperty('featured') === true;\n});\n\n$media = $model->getMedia('images', ['mime_type' => 'image/jpeg']);\n```\n\n## Custom Properties\n\nStore arbitrary metadata on media items:\n\n```php\n// When adding\n$model->addMedia($file)\n    ->withCustomProperties([\n        'alt' => 'Descriptive text',\n        'credits' => 'Photographer Name',\n    ])\n    ->toMediaCollection('images');\n\n// Get/set on existing media\n$media->setCustomProperty('alt', 'Updated text');\n$media->save();\n\n$alt = $media->getCustomProperty('alt');\n$has = $media->hasCustomProperty('alt');\n$media->forgetCustomProperty('alt');\n$media->save();\n```\n\n## Responsive Images\n\nGenerate multiple sizes for optimal loading:\n\n```php\n// On the FileAdder\n$model->addMedia($file)\n    ->withResponsiveImages()\n    ->toMediaCollection('images');\n\n// On a conversion\n$this->addMediaConversion('hero')\n    ->fit(Fit::Max, 1200, 800)\n    ->withResponsiveImages();\n\n// On a collection\n$this->addMediaCollection('photos')\n    ->withResponsiveImages();\n```\n\n### Using in Blade\n```blade\n{{-- Renders img tag with srcset --}}\n{{ $media->toHtml() }}\n\n{{-- With attributes --}}\n{{ $media->img()->attributes(['class' => 'w-full', 'alt' => 'Photo']) }}\n\n{{-- Get srcset string --}}\n<img src=\"{{ $media->getUrl() }}\" srcset=\"{{ $media->getSrcset() }}\" />\n\n{{-- Responsive conversion --}}\n<img src=\"{{ $media->getUrl('hero') }}\" srcset=\"{{ $media->getSrcset('hero') }}\" />\n```\n\n### Placeholder SVG\n```php\n$svg = $media->responsiveImages()->getPlaceholderSvg(); // tiny blurred base64 placeholder\n```\n\n## Managing Media\n\n### Clear a collection\n```php\n$model->clearMediaCollection('images');\n```\n\n### Clear except specific items\n```php\n$model->clearMediaCollectionExcept('images', $mediaToKeep);\n```\n\n### Delete specific media\n```php\n$model->deleteMedia($mediaId);\n```\n\n### Delete all media\n```php\n$model->deleteAllMedia();\n```\n\n### Delete model but keep media files\n```php\n$model->deletePreservingMedia();\n```\n\n### Reorder media\n```php\nMedia::setNewOrder([3, 1, 2]); // media IDs in desired order\n```\n\n### Move/copy media between models\n```php\n$media->move($otherModel, 'images');\n$media->copy($otherModel, 'images');\n```\n\n## Events\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Events\\MediaHasBeenAddedEvent;\nuse Spatie\\MediaLibrary\\Conversions\\Events\\ConversionWillStartEvent;\nuse Spatie\\MediaLibrary\\Conversions\\Events\\ConversionHasBeenCompletedEvent;\nuse Spatie\\MediaLibrary\\MediaCollections\\Events\\CollectionHasBeenClearedEvent;\n```\n\nListen to these events to hook into the media lifecycle:\n```php\nEvent::listen(MediaHasBeenAddedEvent::class, function ($event) {\n    $event->media; // the added Media model\n});\n\nEvent::listen(ConversionHasBeenCompletedEvent::class, function ($event) {\n    $event->media;\n    $event->conversion;\n});\n```\n\n## Configuration\n\nKey `config/media-library.php` options:\n\n```php\nreturn [\n    'disk_name' => 'public',                    // default disk\n    'max_file_size' => 1024 * 1024 * 10,         // 10MB\n    'queue_connection_name' => '',                // queue connection\n    'queue_name' => '',                          // queue name\n    'queue_conversions_by_default' => true,       // queue conversions\n    'media_model' => Spatie\\MediaLibrary\\MediaCollections\\Models\\Media::class,\n    'file_namer' => Spatie\\MediaLibrary\\Support\\FileNamer\\DefaultFileNamer::class,\n    'path_generator' => Spatie\\MediaLibrary\\Support\\PathGenerator\\DefaultPathGenerator::class,\n    'url_generator' => Spatie\\MediaLibrary\\Support\\UrlGenerator\\DefaultUrlGenerator::class,\n    'image_driver' => 'gd',                      // 'gd', 'imagick', or 'vips'\n    'image_optimizers' => [/* optimizer config */],\n    'version_urls' => true,                       // cache busting\n    'default_loading_attribute_value' => null,     // 'lazy' for lazy loading\n];\n```\n\n### Custom Path Generator\n\n```php\nuse Spatie\\MediaLibrary\\Support\\PathGenerator\\PathGenerator;\n\nclass CustomPathGenerator implements PathGenerator\n{\n    public function getPath(Media $media): string\n    {\n        return md5($media->id) . '/';\n    }\n\n    public function getPathForConversions(Media $media): string\n    {\n        return $this->getPath($media) . 'conversions/';\n    }\n\n    public function getPathForResponsiveImages(Media $media): string\n    {\n        return $this->getPath($media) . 'responsive/';\n    }\n}\n```\n\n### Custom File Namer\n\n```php\nuse Spatie\\MediaLibrary\\Support\\FileNamer\\FileNamer;\n\nclass CustomFileNamer extends FileNamer\n{\n    public function originalFileName(string $fileName): string\n    {\n        return Str::slug(pathinfo($fileName, PATHINFO_FILENAME));\n    }\n\n    public function conversionFileName(string $fileName, Conversion $conversion): string\n    {\n        return $this->originalFileName($fileName) . '-' . $conversion->getName();\n    }\n\n    public function responsiveFileName(string $fileName): string\n    {\n        return pathinfo($fileName, PATHINFO_FILENAME);\n    }\n}\n```\n\n### Custom Media Model\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media as BaseMedia;\n\nclass Media extends BaseMedia\n{\n    // Add custom methods, scopes, or override behavior\n}\n```\n\nRegister in config: `'media_model' => App\\Models\\Media::class`\n\n## Downloading Media\n\n### Single file\n```php\nreturn $media->toResponse($request); // download\nreturn $media->toInlineResponse($request); // display inline\nreturn $media->stream(); // stream\n```\n\n### ZIP download of collection\n```php\nuse Spatie\\MediaLibrary\\Support\\MediaStream;\n\nreturn MediaStream::create('photos.zip')\n    ->addMedia($model->getMedia('images'));\n```\n\n## Using with API Resources\n\n```php\nclass PostResource extends JsonResource\n{\n    public function toArray($request): array\n    {\n        return [\n            'id' => $this->id,\n            'title' => $this->title,\n            'image' => $this->getFirstMediaUrl('images'),\n            'thumb' => $this->getFirstMediaUrl('images', 'thumb'),\n            'media' => $this->getMedia('images')->map(function ($media) {\n                return [\n                    'id' => $media->id,\n                    'url' => $media->getUrl(),\n                    'thumb' => $media->getUrl('thumb'),\n                    'name' => $media->name,\n                    'size' => $media->size,\n                    'type' => $media->mime_type,\n                ];\n            }),\n        ];\n    }\n}\n```\n"},"files":{"resources/boost/skills/medialibrary-development/SKILL.md":"---\nname: medialibrary-development\ndescription: Build and work with spatie/laravel-medialibrary features including associating files with Eloquent models, defining media collections and conversions, generating responsive images, and retrieving media URLs and paths.\nlicense: MIT\nmetadata:\n  author: Spatie\n---\n\n# Media Library Development\n\n## Overview\nUse spatie/laravel-medialibrary to associate files with Eloquent models. Supports image/video conversions, responsive images, multiple collections, and various storage disks.\n\n## When to Activate\n- Activate when working with file uploads, media attachments, or image processing in Laravel.\n- Activate when code references `HasMedia`, `InteractsWithMedia`, the `Media` model, or media collections/conversions.\n- Activate when the user wants to add, retrieve, convert, or manage files attached to Eloquent models.\n\n## Scope\n- In scope: media uploads, collections, conversions, responsive images, custom properties, file retrieval, path/URL generation.\n- Out of scope: general file storage without Eloquent association, non-Laravel frameworks.\n\n## Workflow\n1. Identify the task (model setup, adding media, defining conversions, retrieving files, etc.).\n2. Read `references/medialibrary-guide.md` and focus on the relevant section.\n3. Apply the patterns from the reference, keeping code minimal and Laravel-native.\n\n## Core Concepts\n\n### Model Setup\nEvery model that should have media must implement `HasMedia` and use the `InteractsWithMedia` trait:\n\n```php\nuse Spatie\\MediaLibrary\\HasMedia;\nuse Spatie\\MediaLibrary\\InteractsWithMedia;\n\nclass BlogPost extends Model implements HasMedia\n{\n    use InteractsWithMedia;\n}\n```\n\n### Adding Media\n```php\n$blogPost->addMedia($file)->toMediaCollection('images');\n$blogPost->addMediaFromUrl($url)->toMediaCollection('images');\n$blogPost->addMediaFromRequest('file')->toMediaCollection('images');\n```\n\n### Defining Collections\n```php\npublic function registerMediaCollections(): void\n{\n    $this->addMediaCollection('avatar')->singleFile();\n    $this->addMediaCollection('downloads')->useDisk('s3');\n}\n```\n\n### Defining Conversions\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media;\nuse Spatie\\Image\\Enums\\Fit;\n\npublic function registerMediaConversions(?Media $media = null): void\n{\n    $this->addMediaConversion('thumb')\n        ->fit(Fit::Contain, 300, 300)\n        ->nonQueued();\n}\n```\n\n### Retrieving Media\n```php\n$url = $model->getFirstMediaUrl('images');\n$thumbUrl = $model->getFirstMediaUrl('images', 'thumb');\n$allMedia = $model->getMedia('images');\n```\n\n## Do and Don't\n\nDo:\n- Always implement the `HasMedia` interface alongside the `InteractsWithMedia` trait.\n- Use `?Media $media = null` as the parameter for `registerMediaConversions()`.\n- Call `->toMediaCollection()` to finalize adding media.\n- Use `->nonQueued()` for conversions that should run synchronously.\n- Use `->singleFile()` on collections that should only hold one file.\n- Use `Spatie\\Image\\Enums\\Fit` enum values for fit methods.\n\nDon't:\n- Don't forget to run `php artisan vendor:publish --provider=\"Spatie\\MediaLibrary\\MediaLibraryServiceProvider\" --tag=\"medialibrary-migrations\"` before migrating.\n- Don't use `env()` for disk configuration; use `config()` or set it in `config/media-library.php`.\n- Don't call `addMedia()` without calling `toMediaCollection()` — the media won't be saved.\n- Don't reference conversion names that aren't registered in `registerMediaConversions()`.\n\n## References\n- `references/medialibrary-guide.md`\n","resources/boost/skills/medialibrary-development/references/medialibrary-guide.md":"# Laravel Media Library Reference\n\nComplete reference for `spatie/laravel-medialibrary`. Full documentation: https://spatie.be/docs/laravel-medialibrary\n\n## Model Setup\n\nImplement `HasMedia` and use `InteractsWithMedia`:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Spatie\\MediaLibrary\\HasMedia;\nuse Spatie\\MediaLibrary\\InteractsWithMedia;\n\nclass BlogPost extends Model implements HasMedia\n{\n    use InteractsWithMedia;\n\n    public function registerMediaCollections(): void\n    {\n        $this->addMediaCollection('images');\n    }\n\n    public function registerMediaConversions(?Media $media = null): void\n    {\n        $this->addMediaConversion('thumb')\n            ->fit(Fit::Contain, 300, 300);\n    }\n}\n```\n\n## Adding Media\n\n### From uploaded file\n```php\n$model->addMedia($request->file('image'))->toMediaCollection('images');\n```\n\n### From request (shorthand)\n```php\n$model->addMediaFromRequest('image')->toMediaCollection('images');\n```\n\n### From URL\n```php\n$model->addMediaFromUrl('https://example.com/image.jpg')->toMediaCollection('images');\n```\n\n### From string content\n```php\n$model->addMediaFromString('raw content')->usingFileName('file.txt')->toMediaCollection('files');\n```\n\n### From base64\n```php\n$model->addMediaFromBase64($base64Data)->usingFileName('photo.jpg')->toMediaCollection('images');\n```\n\n### From stream\n```php\n$model->addMediaFromStream($stream)->usingFileName('file.pdf')->toMediaCollection('files');\n```\n\n### From existing disk\n```php\n$model->addMediaFromDisk('path/to/file.jpg', 's3')->toMediaCollection('images');\n```\n\n### Multiple files from request\n```php\n$model->addMultipleMediaFromRequest(['images'])->each(function ($fileAdder) {\n    $fileAdder->toMediaCollection('images');\n});\n\n$model->addAllMediaFromRequest()->each(function ($fileAdder) {\n    $fileAdder->toMediaCollection('images');\n});\n```\n\n### Copy instead of move\n```php\n$model->copyMedia($pathToFile)->toMediaCollection('images');\n// or\n$model->addMedia($pathToFile)->preservingOriginal()->toMediaCollection('images');\n```\n\n## FileAdder Options\n\nAll methods are chainable before calling `toMediaCollection()`:\n\n```php\n$model->addMedia($file)\n    ->usingName('Custom Name')              // display name\n    ->usingFileName('custom-name.jpg')      // filename on disk\n    ->setOrder(3)                           // order within collection\n    ->withCustomProperties(['alt' => 'A landscape photo'])\n    ->withManipulations(['thumb' => ['filter' => 'greyscale']])\n    ->withResponsiveImages()                // generate responsive variants\n    ->storingConversionsOnDisk('s3')         // put conversions on different disk\n    ->addCustomHeaders(['CacheControl' => 'max-age=31536000'])\n    ->toMediaCollection('images');\n```\n\n### Store on cloud disk\n```php\n$model->addMedia($file)->toMediaCollectionOnCloudDisk('images');\n```\n\n## Media Collections\n\nDefine in `registerMediaCollections()`:\n\n```php\npublic function registerMediaCollections(): void\n{\n    // Basic collection\n    $this->addMediaCollection('images');\n\n    // Single file (replacing previous on new upload)\n    $this->addMediaCollection('avatar')\n        ->singleFile();\n\n    // Keep only latest N items\n    $this->addMediaCollection('recent_photos')\n        ->onlyKeepLatest(5);\n\n    // Specific disk\n    $this->addMediaCollection('downloads')\n        ->useDisk('s3');\n\n    // With conversions disk\n    $this->addMediaCollection('photos')\n        ->useDisk('s3')\n        ->storeConversionsOnDisk('s3-thumbnails');\n\n    // MIME type restriction\n    $this->addMediaCollection('documents')\n        ->acceptsMimeTypes(['application/pdf', 'application/zip']);\n\n    // Custom validation\n    $this->addMediaCollection('images')\n        ->acceptsFile(function ($file) {\n            return $file->mimeType === 'image/jpeg';\n        });\n\n    // Fallback URL/path when collection is empty\n    $this->addMediaCollection('avatar')\n        ->singleFile()\n        ->useFallbackUrl('/images/default-avatar.jpg')\n        ->useFallbackPath(public_path('/images/default-avatar.jpg'));\n\n    // Enable responsive images for entire collection\n    $this->addMediaCollection('hero_images')\n        ->withResponsiveImages();\n\n    // Collection-specific conversions\n    $this->addMediaCollection('photos')\n        ->registerMediaConversions(function () {\n            $this->addMediaConversion('card')\n                ->fit(Fit::Crop, 400, 400);\n        });\n}\n```\n\n## Media Conversions\n\nDefine in `registerMediaConversions()`:\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media;\nuse Spatie\\Image\\Enums\\Fit;\n\npublic function registerMediaConversions(?Media $media = null): void\n{\n    $this->addMediaConversion('thumb')\n        ->fit(Fit::Contain, 300, 300)\n        ->nonQueued();\n\n    $this->addMediaConversion('preview')\n        ->fit(Fit::Crop, 500, 500)\n        ->withResponsiveImages()\n        ->queued();\n\n    $this->addMediaConversion('banner')\n        ->fit(Fit::Max, 1200, 630)\n        ->performOnCollections('images', 'headers')\n        ->nonQueued()\n        ->sharpen(10);\n\n    // Conditional conversion based on media properties\n    if ($media?->mime_type === 'image/png') {\n        $this->addMediaConversion('png-thumb')\n            ->fit(Fit::Contain, 150, 150);\n    }\n\n    // Keep original format instead of converting to jpg\n    $this->addMediaConversion('web')\n        ->fit(Fit::Max, 800, 800)\n        ->keepOriginalImageFormat();\n\n    // PDF page rendering\n    $this->addMediaConversion('pdf-preview')\n        ->pdfPageNumber(1)\n        ->fit(Fit::Contain, 400, 400);\n\n    // Video frame extraction\n    $this->addMediaConversion('video-thumb')\n        ->extractVideoFrameAtSecond(5)\n        ->fit(Fit::Crop, 300, 300);\n}\n```\n\n### Image Manipulation Methods (via spatie/image)\n\nResizing and fitting:\n- `width(int)`, `height(int)` — constrain dimensions\n- `fit(Fit, int, int)` — fit within bounds using `Fit::Contain`, `Fit::Max`, `Fit::Fill`, `Fit::Stretch`, `Fit::Crop`\n- `crop(int, int)` — crop to exact dimensions\n\nEffects:\n- `sharpen(int)`, `blur(int)`, `pixelate(int)`\n- `greyscale()`, `sepia()`\n- `brightness(int)`, `contrast(int)`, `colorize(int, int, int)`\n\nOrientation:\n- `orientation(int)`, `flip(string)`, `rotate(int)`\n\nFormat:\n- `format(string)` — `'jpg'`, `'png'`, `'webp'`, `'avif'`\n- `quality(int)` — 1-100\n\nOther:\n- `border(int, string, string)`, `watermark(string)`\n- `optimize()`, `nonOptimized()`\n\n### Conversion Configuration\n\n- `performOnCollections('col1', 'col2')` — limit to specific collections\n- `queued()` / `nonQueued()` — run async or sync\n- `withResponsiveImages()` — also generate responsive variants for this conversion\n- `keepOriginalImageFormat()` — preserve png/webp/gif instead of converting to jpg\n- `pdfPageNumber(int)` — which PDF page to render\n- `extractVideoFrameAtSecond(int)` — video thumbnail timing\n\n## Retrieving Media\n\n### Getting media items\n```php\n$media = $model->getMedia('images');                    // all in collection\n$first = $model->getFirstMedia('images');               // first item\n$last  = $model->getLastMedia('images');                // last item\n$has   = $model->hasMedia('images');                    // boolean check\n```\n\n### Getting URLs\n```php\n$url     = $model->getFirstMediaUrl('images');           // original URL\n$thumbUrl = $model->getFirstMediaUrl('images', 'thumb'); // conversion URL\n$lastUrl  = $model->getLastMediaUrl('images', 'thumb');\n```\n\n### Getting paths\n```php\n$path     = $model->getFirstMediaPath('images');\n$thumbPath = $model->getFirstMediaPath('images', 'thumb');\n```\n\n### Temporary URLs (S3)\n```php\n$tempUrl = $model->getFirstTemporaryUrl(\n    now()->addMinutes(30),\n    'images',\n    'thumb'\n);\n```\n\n### Fallback URLs\n```php\n$url = $model->getFallbackMediaUrl('avatar');\n```\n\n### From the Media model\n```php\n$media = $model->getFirstMedia('images');\n\n$media->getUrl();                    // original URL\n$media->getUrl('thumb');             // conversion URL\n$media->getPath();                   // disk path\n$media->getFullUrl();                // full URL with domain\n$media->getTemporaryUrl(now()->addMinutes(30));\n$media->hasGeneratedConversion('thumb');  // check if conversion exists\n```\n\n### Filtering media\n```php\n$media = $model->getMedia('images', function (Media $media) {\n    return $media->getCustomProperty('featured') === true;\n});\n\n$media = $model->getMedia('images', ['mime_type' => 'image/jpeg']);\n```\n\n## Custom Properties\n\nStore arbitrary metadata on media items:\n\n```php\n// When adding\n$model->addMedia($file)\n    ->withCustomProperties([\n        'alt' => 'Descriptive text',\n        'credits' => 'Photographer Name',\n    ])\n    ->toMediaCollection('images');\n\n// Get/set on existing media\n$media->setCustomProperty('alt', 'Updated text');\n$media->save();\n\n$alt = $media->getCustomProperty('alt');\n$has = $media->hasCustomProperty('alt');\n$media->forgetCustomProperty('alt');\n$media->save();\n```\n\n## Responsive Images\n\nGenerate multiple sizes for optimal loading:\n\n```php\n// On the FileAdder\n$model->addMedia($file)\n    ->withResponsiveImages()\n    ->toMediaCollection('images');\n\n// On a conversion\n$this->addMediaConversion('hero')\n    ->fit(Fit::Max, 1200, 800)\n    ->withResponsiveImages();\n\n// On a collection\n$this->addMediaCollection('photos')\n    ->withResponsiveImages();\n```\n\n### Using in Blade\n```blade\n{{-- Renders img tag with srcset --}}\n{{ $media->toHtml() }}\n\n{{-- With attributes --}}\n{{ $media->img()->attributes(['class' => 'w-full', 'alt' => 'Photo']) }}\n\n{{-- Get srcset string --}}\n<img src=\"{{ $media->getUrl() }}\" srcset=\"{{ $media->getSrcset() }}\" />\n\n{{-- Responsive conversion --}}\n<img src=\"{{ $media->getUrl('hero') }}\" srcset=\"{{ $media->getSrcset('hero') }}\" />\n```\n\n### Placeholder SVG\n```php\n$svg = $media->responsiveImages()->getPlaceholderSvg(); // tiny blurred base64 placeholder\n```\n\n## Managing Media\n\n### Clear a collection\n```php\n$model->clearMediaCollection('images');\n```\n\n### Clear except specific items\n```php\n$model->clearMediaCollectionExcept('images', $mediaToKeep);\n```\n\n### Delete specific media\n```php\n$model->deleteMedia($mediaId);\n```\n\n### Delete all media\n```php\n$model->deleteAllMedia();\n```\n\n### Delete model but keep media files\n```php\n$model->deletePreservingMedia();\n```\n\n### Reorder media\n```php\nMedia::setNewOrder([3, 1, 2]); // media IDs in desired order\n```\n\n### Move/copy media between models\n```php\n$media->move($otherModel, 'images');\n$media->copy($otherModel, 'images');\n```\n\n## Events\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Events\\MediaHasBeenAddedEvent;\nuse Spatie\\MediaLibrary\\Conversions\\Events\\ConversionWillStartEvent;\nuse Spatie\\MediaLibrary\\Conversions\\Events\\ConversionHasBeenCompletedEvent;\nuse Spatie\\MediaLibrary\\MediaCollections\\Events\\CollectionHasBeenClearedEvent;\n```\n\nListen to these events to hook into the media lifecycle:\n```php\nEvent::listen(MediaHasBeenAddedEvent::class, function ($event) {\n    $event->media; // the added Media model\n});\n\nEvent::listen(ConversionHasBeenCompletedEvent::class, function ($event) {\n    $event->media;\n    $event->conversion;\n});\n```\n\n## Configuration\n\nKey `config/media-library.php` options:\n\n```php\nreturn [\n    'disk_name' => 'public',                    // default disk\n    'max_file_size' => 1024 * 1024 * 10,         // 10MB\n    'queue_connection_name' => '',                // queue connection\n    'queue_name' => '',                          // queue name\n    'queue_conversions_by_default' => true,       // queue conversions\n    'media_model' => Spatie\\MediaLibrary\\MediaCollections\\Models\\Media::class,\n    'file_namer' => Spatie\\MediaLibrary\\Support\\FileNamer\\DefaultFileNamer::class,\n    'path_generator' => Spatie\\MediaLibrary\\Support\\PathGenerator\\DefaultPathGenerator::class,\n    'url_generator' => Spatie\\MediaLibrary\\Support\\UrlGenerator\\DefaultUrlGenerator::class,\n    'image_driver' => 'gd',                      // 'gd', 'imagick', or 'vips'\n    'image_optimizers' => [/* optimizer config */],\n    'version_urls' => true,                       // cache busting\n    'default_loading_attribute_value' => null,     // 'lazy' for lazy loading\n];\n```\n\n### Custom Path Generator\n\n```php\nuse Spatie\\MediaLibrary\\Support\\PathGenerator\\PathGenerator;\n\nclass CustomPathGenerator implements PathGenerator\n{\n    public function getPath(Media $media): string\n    {\n        return md5($media->id) . '/';\n    }\n\n    public function getPathForConversions(Media $media): string\n    {\n        return $this->getPath($media) . 'conversions/';\n    }\n\n    public function getPathForResponsiveImages(Media $media): string\n    {\n        return $this->getPath($media) . 'responsive/';\n    }\n}\n```\n\n### Custom File Namer\n\n```php\nuse Spatie\\MediaLibrary\\Support\\FileNamer\\FileNamer;\n\nclass CustomFileNamer extends FileNamer\n{\n    public function originalFileName(string $fileName): string\n    {\n        return Str::slug(pathinfo($fileName, PATHINFO_FILENAME));\n    }\n\n    public function conversionFileName(string $fileName, Conversion $conversion): string\n    {\n        return $this->originalFileName($fileName) . '-' . $conversion->getName();\n    }\n\n    public function responsiveFileName(string $fileName): string\n    {\n        return pathinfo($fileName, PATHINFO_FILENAME);\n    }\n}\n```\n\n### Custom Media Model\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media as BaseMedia;\n\nclass Media extends BaseMedia\n{\n    // Add custom methods, scopes, or override behavior\n}\n```\n\nRegister in config: `'media_model' => App\\Models\\Media::class`\n\n## Downloading Media\n\n### Single file\n```php\nreturn $media->toResponse($request); // download\nreturn $media->toInlineResponse($request); // display inline\nreturn $media->stream(); // stream\n```\n\n### ZIP download of collection\n```php\nuse Spatie\\MediaLibrary\\Support\\MediaStream;\n\nreturn MediaStream::create('photos.zip')\n    ->addMedia($model->getMedia('images'));\n```\n\n## Using with API Resources\n\n```php\nclass PostResource extends JsonResource\n{\n    public function toArray($request): array\n    {\n        return [\n            'id' => $this->id,\n            'title' => $this->title,\n            'image' => $this->getFirstMediaUrl('images'),\n            'thumb' => $this->getFirstMediaUrl('images', 'thumb'),\n            'media' => $this->getMedia('images')->map(function ($media) {\n                return [\n                    'id' => $media->id,\n                    'url' => $media->getUrl(),\n                    'thumb' => $media->getUrl('thumb'),\n                    'name' => $media->name,\n                    'size' => $media->size,\n                    'type' => $media->mime_type,\n                ];\n            }),\n        ];\n    }\n}\n```\n"},"items":[{"name":"medialibrary-guide.md","path":"resources/boost/skills/medialibrary-development/references/medialibrary-guide.md","rawUrl":"https://raw.githubusercontent.com/spatie/laravel-medialibrary/HEAD/resources/boost/skills/medialibrary-development/references/medialibrary-guide.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Laravel Media Library Reference\n\nComplete reference for `spatie/laravel-medialibrary`. Full documentation: https://spatie.be/docs/laravel-medialibrary\n\n## Model Setup\n\nImplement `HasMedia` and use `InteractsWithMedia`:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Spatie\\MediaLibrary\\HasMedia;\nuse Spatie\\MediaLibrary\\InteractsWithMedia;\n\nclass BlogPost extends Model implements HasMedia\n{\n    use InteractsWithMedia;\n\n    public function registerMediaCollections(): void\n    {\n        $this->addMediaCollection('images');\n    }\n\n    public function registerMediaConversions(?Media $media = null): void\n    {\n        $this->addMediaConversion('thumb')\n            ->fit(Fit::Contain, 300, 300);\n    }\n}\n```\n\n## Adding Media\n\n### From uploaded file\n```php\n$model->addMedia($request->file('image'))->toMediaCollection('images');\n```\n\n### From request (shorthand)\n```php\n$model->addMediaFromRequest('image')->toMediaCollection('images');\n```\n\n### From URL\n```php\n$model->addMediaFromUrl('https://example.com/image.jpg')->toMediaCollection('images');\n```\n\n### From string content\n```php\n$model->addMediaFromString('raw content')->usingFileName('file.txt')->toMediaCollection('files');\n```\n\n### From base64\n```php\n$model->addMediaFromBase64($base64Data)->usingFileName('photo.jpg')->toMediaCollection('images');\n```\n\n### From stream\n```php\n$model->addMediaFromStream($stream)->usingFileName('file.pdf')->toMediaCollection('files');\n```\n\n### From existing disk\n```php\n$model->addMediaFromDisk('path/to/file.jpg', 's3')->toMediaCollection('images');\n```\n\n### Multiple files from request\n```php\n$model->addMultipleMediaFromRequest(['images'])->each(function ($fileAdder) {\n    $fileAdder->toMediaCollection('images');\n});\n\n$model->addAllMediaFromRequest()->each(function ($fileAdder) {\n    $fileAdder->toMediaCollection('images');\n});\n```\n\n### Copy instead of move\n```php\n$model->copyMedia($pathToFile)->toMediaCollection('images');\n// or\n$model->addMedia($pathToFile)->preservingOriginal()->toMediaCollection('images');\n```\n\n## FileAdder Options\n\nAll methods are chainable before calling `toMediaCollection()`:\n\n```php\n$model->addMedia($file)\n    ->usingName('Custom Name')              // display name\n    ->usingFileName('custom-name.jpg')      // filename on disk\n    ->setOrder(3)                           // order within collection\n    ->withCustomProperties(['alt' => 'A landscape photo'])\n    ->withManipulations(['thumb' => ['filter' => 'greyscale']])\n    ->withResponsiveImages()                // generate responsive variants\n    ->storingConversionsOnDisk('s3')         // put conversions on different disk\n    ->addCustomHeaders(['CacheControl' => 'max-age=31536000'])\n    ->toMediaCollection('images');\n```\n\n### Store on cloud disk\n```php\n$model->addMedia($file)->toMediaCollectionOnCloudDisk('images');\n```\n\n## Media Collections\n\nDefine in `registerMediaCollections()`:\n\n```php\npublic function registerMediaCollections(): void\n{\n    // Basic collection\n    $this->addMediaCollection('images');\n\n    // Single file (replacing previous on new upload)\n    $this->addMediaCollection('avatar')\n        ->singleFile();\n\n    // Keep only latest N items\n    $this->addMediaCollection('recent_photos')\n        ->onlyKeepLatest(5);\n\n    // Specific disk\n    $this->addMediaCollection('downloads')\n        ->useDisk('s3');\n\n    // With conversions disk\n    $this->addMediaCollection('photos')\n        ->useDisk('s3')\n        ->storeConversionsOnDisk('s3-thumbnails');\n\n    // MIME type restriction\n    $this->addMediaCollection('documents')\n        ->acceptsMimeTypes(['application/pdf', 'application/zip']);\n\n    // Custom validation\n    $this->addMediaCollection('images')\n        ->acceptsFile(function ($file) {\n            return $file->mimeType === 'image/jpeg';\n        });\n\n    // Fallback URL/path when collection is empty\n    $this->addMediaCollection('avatar')\n        ->singleFile()\n        ->useFallbackUrl('/images/default-avatar.jpg')\n        ->useFallbackPath(public_path('/images/default-avatar.jpg'));\n\n    // Enable responsive images for entire collection\n    $this->addMediaCollection('hero_images')\n        ->withResponsiveImages();\n\n    // Collection-specific conversions\n    $this->addMediaCollection('photos')\n        ->registerMediaConversions(function () {\n            $this->addMediaConversion('card')\n                ->fit(Fit::Crop, 400, 400);\n        });\n}\n```\n\n## Media Conversions\n\nDefine in `registerMediaConversions()`:\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media;\nuse Spatie\\Image\\Enums\\Fit;\n\npublic function registerMediaConversions(?Media $media = null): void\n{\n    $this->addMediaConversion('thumb')\n        ->fit(Fit::Contain, 300, 300)\n        ->nonQueued();\n\n    $this->addMediaConversion('preview')\n        ->fit(Fit::Crop, 500, 500)\n        ->withResponsiveImages()\n        ->queued();\n\n    $this->addMediaConversion('banner')\n        ->fit(Fit::Max, 1200, 630)\n        ->performOnCollections('images', 'headers')\n        ->nonQueued()\n        ->sharpen(10);\n\n    // Conditional conversion based on media properties\n    if ($media?->mime_type === 'image/png') {\n        $this->addMediaConversion('png-thumb')\n            ->fit(Fit::Contain, 150, 150);\n    }\n\n    // Keep original format instead of converting to jpg\n    $this->addMediaConversion('web')\n        ->fit(Fit::Max, 800, 800)\n        ->keepOriginalImageFormat();\n\n    // PDF page rendering\n    $this->addMediaConversion('pdf-preview')\n        ->pdfPageNumber(1)\n        ->fit(Fit::Contain, 400, 400);\n\n    // Video frame extraction\n    $this->addMediaConversion('video-thumb')\n        ->extractVideoFrameAtSecond(5)\n        ->fit(Fit::Crop, 300, 300);\n}\n```\n\n### Image Manipulation Methods (via spatie/image)\n\nResizing and fitting:\n- `width(int)`, `height(int)` — constrain dimensions\n- `fit(Fit, int, int)` — fit within bounds using `Fit::Contain`, `Fit::Max`, `Fit::Fill`, `Fit::Stretch`, `Fit::Crop`\n- `crop(int, int)` — crop to exact dimensions\n\nEffects:\n- `sharpen(int)`, `blur(int)`, `pixelate(int)`\n- `greyscale()`, `sepia()`\n- `brightness(int)`, `contrast(int)`, `colorize(int, int, int)`\n\nOrientation:\n- `orientation(int)`, `flip(string)`, `rotate(int)`\n\nFormat:\n- `format(string)` — `'jpg'`, `'png'`, `'webp'`, `'avif'`\n- `quality(int)` — 1-100\n\nOther:\n- `border(int, string, string)`, `watermark(string)`\n- `optimize()`, `nonOptimized()`\n\n### Conversion Configuration\n\n- `performOnCollections('col1', 'col2')` — limit to specific collections\n- `queued()` / `nonQueued()` — run async or sync\n- `withResponsiveImages()` — also generate responsive variants for this conversion\n- `keepOriginalImageFormat()` — preserve png/webp/gif instead of converting to jpg\n- `pdfPageNumber(int)` — which PDF page to render\n- `extractVideoFrameAtSecond(int)` — video thumbnail timing\n\n## Retrieving Media\n\n### Getting media items\n```php\n$media = $model->getMedia('images');                    // all in collection\n$first = $model->getFirstMedia('images');               // first item\n$last  = $model->getLastMedia('images');                // last item\n$has   = $model->hasMedia('images');                    // boolean check\n```\n\n### Getting URLs\n```php\n$url     = $model->getFirstMediaUrl('images');           // original URL\n$thumbUrl = $model->getFirstMediaUrl('images', 'thumb'); // conversion URL\n$lastUrl  = $model->getLastMediaUrl('images', 'thumb');\n```\n\n### Getting paths\n```php\n$path     = $model->getFirstMediaPath('images');\n$thumbPath = $model->getFirstMediaPath('images', 'thumb');\n```\n\n### Temporary URLs (S3)\n```php\n$tempUrl = $model->getFirstTemporaryUrl(\n    now()->addMinutes(30),\n    'images',\n    'thumb'\n);\n```\n\n### Fallback URLs\n```php\n$url = $model->getFallbackMediaUrl('avatar');\n```\n\n### From the Media model\n```php\n$media = $model->getFirstMedia('images');\n\n$media->getUrl();                    // original URL\n$media->getUrl('thumb');             // conversion URL\n$media->getPath();                   // disk path\n$media->getFullUrl();                // full URL with domain\n$media->getTemporaryUrl(now()->addMinutes(30));\n$media->hasGeneratedConversion('thumb');  // check if conversion exists\n```\n\n### Filtering media\n```php\n$media = $model->getMedia('images', function (Media $media) {\n    return $media->getCustomProperty('featured') === true;\n});\n\n$media = $model->getMedia('images', ['mime_type' => 'image/jpeg']);\n```\n\n## Custom Properties\n\nStore arbitrary metadata on media items:\n\n```php\n// When adding\n$model->addMedia($file)\n    ->withCustomProperties([\n        'alt' => 'Descriptive text',\n        'credits' => 'Photographer Name',\n    ])\n    ->toMediaCollection('images');\n\n// Get/set on existing media\n$media->setCustomProperty('alt', 'Updated text');\n$media->save();\n\n$alt = $media->getCustomProperty('alt');\n$has = $media->hasCustomProperty('alt');\n$media->forgetCustomProperty('alt');\n$media->save();\n```\n\n## Responsive Images\n\nGenerate multiple sizes for optimal loading:\n\n```php\n// On the FileAdder\n$model->addMedia($file)\n    ->withResponsiveImages()\n    ->toMediaCollection('images');\n\n// On a conversion\n$this->addMediaConversion('hero')\n    ->fit(Fit::Max, 1200, 800)\n    ->withResponsiveImages();\n\n// On a collection\n$this->addMediaCollection('photos')\n    ->withResponsiveImages();\n```\n\n### Using in Blade\n```blade\n{{-- Renders img tag with srcset --}}\n{{ $media->toHtml() }}\n\n{{-- With attributes --}}\n{{ $media->img()->attributes(['class' => 'w-full', 'alt' => 'Photo']) }}\n\n{{-- Get srcset string --}}\n<img src=\"{{ $media->getUrl() }}\" srcset=\"{{ $media->getSrcset() }}\" />\n\n{{-- Responsive conversion --}}\n<img src=\"{{ $media->getUrl('hero') }}\" srcset=\"{{ $media->getSrcset('hero') }}\" />\n```\n\n### Placeholder SVG\n```php\n$svg = $media->responsiveImages()->getPlaceholderSvg(); // tiny blurred base64 placeholder\n```\n\n## Managing Media\n\n### Clear a collection\n```php\n$model->clearMediaCollection('images');\n```\n\n### Clear except specific items\n```php\n$model->clearMediaCollectionExcept('images', $mediaToKeep);\n```\n\n### Delete specific media\n```php\n$model->deleteMedia($mediaId);\n```\n\n### Delete all media\n```php\n$model->deleteAllMedia();\n```\n\n### Delete model but keep media files\n```php\n$model->deletePreservingMedia();\n```\n\n### Reorder media\n```php\nMedia::setNewOrder([3, 1, 2]); // media IDs in desired order\n```\n\n### Move/copy media between models\n```php\n$media->move($otherModel, 'images');\n$media->copy($otherModel, 'images');\n```\n\n## Events\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Events\\MediaHasBeenAddedEvent;\nuse Spatie\\MediaLibrary\\Conversions\\Events\\ConversionWillStartEvent;\nuse Spatie\\MediaLibrary\\Conversions\\Events\\ConversionHasBeenCompletedEvent;\nuse Spatie\\MediaLibrary\\MediaCollections\\Events\\CollectionHasBeenClearedEvent;\n```\n\nListen to these events to hook into the media lifecycle:\n```php\nEvent::listen(MediaHasBeenAddedEvent::class, function ($event) {\n    $event->media; // the added Media model\n});\n\nEvent::listen(ConversionHasBeenCompletedEvent::class, function ($event) {\n    $event->media;\n    $event->conversion;\n});\n```\n\n## Configuration\n\nKey `config/media-library.php` options:\n\n```php\nreturn [\n    'disk_name' => 'public',                    // default disk\n    'max_file_size' => 1024 * 1024 * 10,         // 10MB\n    'queue_connection_name' => '',                // queue connection\n    'queue_name' => '',                          // queue name\n    'queue_conversions_by_default' => true,       // queue conversions\n    'media_model' => Spatie\\MediaLibrary\\MediaCollections\\Models\\Media::class,\n    'file_namer' => Spatie\\MediaLibrary\\Support\\FileNamer\\DefaultFileNamer::class,\n    'path_generator' => Spatie\\MediaLibrary\\Support\\PathGenerator\\DefaultPathGenerator::class,\n    'url_generator' => Spatie\\MediaLibrary\\Support\\UrlGenerator\\DefaultUrlGenerator::class,\n    'image_driver' => 'gd',                      // 'gd', 'imagick', or 'vips'\n    'image_optimizers' => [/* optimizer config */],\n    'version_urls' => true,                       // cache busting\n    'default_loading_attribute_value' => null,     // 'lazy' for lazy loading\n];\n```\n\n### Custom Path Generator\n\n```php\nuse Spatie\\MediaLibrary\\Support\\PathGenerator\\PathGenerator;\n\nclass CustomPathGenerator implements PathGenerator\n{\n    public function getPath(Media $media): string\n    {\n        return md5($media->id) . '/';\n    }\n\n    public function getPathForConversions(Media $media): string\n    {\n        return $this->getPath($media) . 'conversions/';\n    }\n\n    public function getPathForResponsiveImages(Media $media): string\n    {\n        return $this->getPath($media) . 'responsive/';\n    }\n}\n```\n\n### Custom File Namer\n\n```php\nuse Spatie\\MediaLibrary\\Support\\FileNamer\\FileNamer;\n\nclass CustomFileNamer extends FileNamer\n{\n    public function originalFileName(string $fileName): string\n    {\n        return Str::slug(pathinfo($fileName, PATHINFO_FILENAME));\n    }\n\n    public function conversionFileName(string $fileName, Conversion $conversion): string\n    {\n        return $this->originalFileName($fileName) . '-' . $conversion->getName();\n    }\n\n    public function responsiveFileName(string $fileName): string\n    {\n        return pathinfo($fileName, PATHINFO_FILENAME);\n    }\n}\n```\n\n### Custom Media Model\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media as BaseMedia;\n\nclass Media extends BaseMedia\n{\n    // Add custom methods, scopes, or override behavior\n}\n```\n\nRegister in config: `'media_model' => App\\Models\\Media::class`\n\n## Downloading Media\n\n### Single file\n```php\nreturn $media->toResponse($request); // download\nreturn $media->toInlineResponse($request); // display inline\nreturn $media->stream(); // stream\n```\n\n### ZIP download of collection\n```php\nuse Spatie\\MediaLibrary\\Support\\MediaStream;\n\nreturn MediaStream::create('photos.zip')\n    ->addMedia($model->getMedia('images'));\n```\n\n## Using with API Resources\n\n```php\nclass PostResource extends JsonResource\n{\n    public function toArray($request): array\n    {\n        return [\n            'id' => $this->id,\n            'title' => $this->title,\n            'image' => $this->getFirstMediaUrl('images'),\n            'thumb' => $this->getFirstMediaUrl('images', 'thumb'),\n            'media' => $this->getMedia('images')->map(function ($media) {\n                return [\n                    'id' => $media->id,\n                    'url' => $media->getUrl(),\n                    'thumb' => $media->getUrl('thumb'),\n                    'name' => $media->name,\n                    'size' => $media->size,\n                    'type' => $media->mime_type,\n                ];\n            }),\n        ];\n    }\n}\n```\n","isInternal":false,"tokens":3624,"sizeBytes":14619},{"name":"SKILL.md","path":"resources/boost/skills/medialibrary-development/SKILL.md","rawUrl":"https://raw.githubusercontent.com/spatie/laravel-medialibrary/HEAD/resources/boost/skills/medialibrary-development/SKILL.md","title":"medialibrary-development","category":"anthropic-skill","format":"markdown","content":"---\nname: medialibrary-development\ndescription: Build and work with spatie/laravel-medialibrary features including associating files with Eloquent models, defining media collections and conversions, generating responsive images, and retrieving media URLs and paths.\nlicense: MIT\nmetadata:\n  author: Spatie\n---\n\n# Media Library Development\n\n## Overview\nUse spatie/laravel-medialibrary to associate files with Eloquent models. Supports image/video conversions, responsive images, multiple collections, and various storage disks.\n\n## When to Activate\n- Activate when working with file uploads, media attachments, or image processing in Laravel.\n- Activate when code references `HasMedia`, `InteractsWithMedia`, the `Media` model, or media collections/conversions.\n- Activate when the user wants to add, retrieve, convert, or manage files attached to Eloquent models.\n\n## Scope\n- In scope: media uploads, collections, conversions, responsive images, custom properties, file retrieval, path/URL generation.\n- Out of scope: general file storage without Eloquent association, non-Laravel frameworks.\n\n## Workflow\n1. Identify the task (model setup, adding media, defining conversions, retrieving files, etc.).\n2. Read `references/medialibrary-guide.md` and focus on the relevant section.\n3. Apply the patterns from the reference, keeping code minimal and Laravel-native.\n\n## Core Concepts\n\n### Model Setup\nEvery model that should have media must implement `HasMedia` and use the `InteractsWithMedia` trait:\n\n```php\nuse Spatie\\MediaLibrary\\HasMedia;\nuse Spatie\\MediaLibrary\\InteractsWithMedia;\n\nclass BlogPost extends Model implements HasMedia\n{\n    use InteractsWithMedia;\n}\n```\n\n### Adding Media\n```php\n$blogPost->addMedia($file)->toMediaCollection('images');\n$blogPost->addMediaFromUrl($url)->toMediaCollection('images');\n$blogPost->addMediaFromRequest('file')->toMediaCollection('images');\n```\n\n### Defining Collections\n```php\npublic function registerMediaCollections(): void\n{\n    $this->addMediaCollection('avatar')->singleFile();\n    $this->addMediaCollection('downloads')->useDisk('s3');\n}\n```\n\n### Defining Conversions\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media;\nuse Spatie\\Image\\Enums\\Fit;\n\npublic function registerMediaConversions(?Media $media = null): void\n{\n    $this->addMediaConversion('thumb')\n        ->fit(Fit::Contain, 300, 300)\n        ->nonQueued();\n}\n```\n\n### Retrieving Media\n```php\n$url = $model->getFirstMediaUrl('images');\n$thumbUrl = $model->getFirstMediaUrl('images', 'thumb');\n$allMedia = $model->getMedia('images');\n```\n\n## Do and Don't\n\nDo:\n- Always implement the `HasMedia` interface alongside the `InteractsWithMedia` trait.\n- Use `?Media $media = null` as the parameter for `registerMediaConversions()`.\n- Call `->toMediaCollection()` to finalize adding media.\n- Use `->nonQueued()` for conversions that should run synchronously.\n- Use `->singleFile()` on collections that should only hold one file.\n- Use `Spatie\\Image\\Enums\\Fit` enum values for fit methods.\n\nDon't:\n- Don't forget to run `php artisan vendor:publish --provider=\"Spatie\\MediaLibrary\\MediaLibraryServiceProvider\" --tag=\"medialibrary-migrations\"` before migrating.\n- Don't use `env()` for disk configuration; use `config()` or set it in `config/media-library.php`.\n- Don't call `addMedia()` without calling `toMediaCollection()` — the media won't be saved.\n- Don't reference conversion names that aren't registered in `registerMediaConversions()`.\n\n## References\n- `references/medialibrary-guide.md`\n","frontmatter":{"name":"medialibrary-development","description":"Build and work with spatie/laravel-medialibrary features including associating files with Eloquent models, defining media collections and conversions, generating responsive images, and retrieving media URLs and paths.","license":"MIT","metadata":{"author":"Spatie"}},"isInternal":false,"tokens":820,"sizeBytes":3511}],"systemPromptSnippet":"<yakaai_skills repo=\"spatie/laravel-medialibrary\">\n<!-- File: resources/boost/skills/medialibrary-development/references/medialibrary-guide.md (Tokens: ~3624 | Category: anthropic-skill) -->\n# Laravel Media Library Reference\n\nComplete reference for `spatie/laravel-medialibrary`. Full documentation: https://spatie.be/docs/laravel-medialibrary\n\n## Model Setup\n\nImplement `HasMedia` and use `InteractsWithMedia`:\n\n```php\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Spatie\\MediaLibrary\\HasMedia;\nuse Spatie\\MediaLibrary\\InteractsWithMedia;\n\nclass BlogPost extends Model implements HasMedia\n{\n    use InteractsWithMedia;\n\n    public function registerMediaCollections(): void\n    {\n        $this->addMediaCollection('images');\n    }\n\n    public function registerMediaConversions(?Media $media = null): void\n    {\n        $this->addMediaConversion('thumb')\n            ->fit(Fit::Contain, 300, 300);\n    }\n}\n```\n\n## Adding Media\n\n### From uploaded file\n```php\n$model->addMedia($request->file('image'))->toMediaCollection('images');\n```\n\n### From request (shorthand)\n```php\n$model->addMediaFromRequest('image')->toMediaCollection('images');\n```\n\n### From URL\n```php\n$model->addMediaFromUrl('https://example.com/image.jpg')->toMediaCollection('images');\n```\n\n### From string content\n```php\n$model->addMediaFromString('raw content')->usingFileName('file.txt')->toMediaCollection('files');\n```\n\n### From base64\n```php\n$model->addMediaFromBase64($base64Data)->usingFileName('photo.jpg')->toMediaCollection('images');\n```\n\n### From stream\n```php\n$model->addMediaFromStream($stream)->usingFileName('file.pdf')->toMediaCollection('files');\n```\n\n### From existing disk\n```php\n$model->addMediaFromDisk('path/to/file.jpg', 's3')->toMediaCollection('images');\n```\n\n### Multiple files from request\n```php\n$model->addMultipleMediaFromRequest(['images'])->each(function ($fileAdder) {\n    $fileAdder->toMediaCollection('images');\n});\n\n$model->addAllMediaFromRequest()->each(function ($fileAdder) {\n    $fileAdder->toMediaCollection('images');\n});\n```\n\n### Copy instead of move\n```php\n$model->copyMedia($pathToFile)->toMediaCollection('images');\n// or\n$model->addMedia($pathToFile)->preservingOriginal()->toMediaCollection('images');\n```\n\n## FileAdder Options\n\nAll methods are chainable before calling `toMediaCollection()`:\n\n```php\n$model->addMedia($file)\n    ->usingName('Custom Name')              // display name\n    ->usingFileName('custom-name.jpg')      // filename on disk\n    ->setOrder(3)                           // order within collection\n    ->withCustomProperties(['alt' => 'A landscape photo'])\n    ->withManipulations(['thumb' => ['filter' => 'greyscale']])\n    ->withResponsiveImages()                // generate responsive variants\n    ->storingConversionsOnDisk('s3')         // put conversions on different disk\n    ->addCustomHeaders(['CacheControl' => 'max-age=31536000'])\n    ->toMediaCollection('images');\n```\n\n### Store on cloud disk\n```php\n$model->addMedia($file)->toMediaCollectionOnCloudDisk('images');\n```\n\n## Media Collections\n\nDefine in `registerMediaCollections()`:\n\n```php\npublic function registerMediaCollections(): void\n{\n    // Basic collection\n    $this->addMediaCollection('images');\n\n    // Single file (replacing previous on new upload)\n    $this->addMediaCollection('avatar')\n        ->singleFile();\n\n    // Keep only latest N items\n    $this->addMediaCollection('recent_photos')\n        ->onlyKeepLatest(5);\n\n    // Specific disk\n    $this->addMediaCollection('downloads')\n        ->useDisk('s3');\n\n    // With conversions disk\n    $this->addMediaCollection('photos')\n        ->useDisk('s3')\n        ->storeConversionsOnDisk('s3-thumbnails');\n\n    // MIME type restriction\n    $this->addMediaCollection('documents')\n        ->acceptsMimeTypes(['application/pdf', 'application/zip']);\n\n    // Custom validation\n    $this->addMediaCollection('images')\n        ->acceptsFile(function ($file) {\n            return $file->mimeType === 'image/jpeg';\n        });\n\n    // Fallback URL/path when collection is empty\n    $this->addMediaCollection('avatar')\n        ->singleFile()\n        ->useFallbackUrl('/images/default-avatar.jpg')\n        ->useFallbackPath(public_path('/images/default-avatar.jpg'));\n\n    // Enable responsive images for entire collection\n    $this->addMediaCollection('hero_images')\n        ->withResponsiveImages();\n\n    // Collection-specific conversions\n    $this->addMediaCollection('photos')\n        ->registerMediaConversions(function () {\n            $this->addMediaConversion('card')\n                ->fit(Fit::Crop, 400, 400);\n        });\n}\n```\n\n## Media Conversions\n\nDefine in `registerMediaConversions()`:\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media;\nuse Spatie\\Image\\Enums\\Fit;\n\npublic function registerMediaConversions(?Media $media = null): void\n{\n    $this->addMediaConversion('thumb')\n        ->fit(Fit::Contain, 300, 300)\n        ->nonQueued();\n\n    $this->addMediaConversion('preview')\n        ->fit(Fit::Crop, 500, 500)\n        ->withResponsiveImages()\n        ->queued();\n\n    $this->addMediaConversion('banner')\n        ->fit(Fit::Max, 1200, 630)\n        ->performOnCollections('images', 'headers')\n        ->nonQueued()\n        ->sharpen(10);\n\n    // Conditional conversion based on media properties\n    if ($media?->mime_type === 'image/png') {\n        $this->addMediaConversion('png-thumb')\n            ->fit(Fit::Contain, 150, 150);\n    }\n\n    // Keep original format instead of converting to jpg\n    $this->addMediaConversion('web')\n        ->fit(Fit::Max, 800, 800)\n        ->keepOriginalImageFormat();\n\n    // PDF page rendering\n    $this->addMediaConversion('pdf-preview')\n        ->pdfPageNumber(1)\n        ->fit(Fit::Contain, 400, 400);\n\n    // Video frame extraction\n    $this->addMediaConversion('video-thumb')\n        ->extractVideoFrameAtSecond(5)\n        ->fit(Fit::Crop, 300, 300);\n}\n```\n\n### Image Manipulation Methods (via spatie/image)\n\nResizing and fitting:\n- `width(int)`, `height(int)` — constrain dimensions\n- `fit(Fit, int, int)` — fit within bounds using `Fit::Contain`, `Fit::Max`, `Fit::Fill`, `Fit::Stretch`, `Fit::Crop`\n- `crop(int, int)` — crop to exact dimensions\n\nEffects:\n- `sharpen(int)`, `blur(int)`, `pixelate(int)`\n- `greyscale()`, `sepia()`\n- `brightness(int)`, `contrast(int)`, `colorize(int, int, int)`\n\nOrientation:\n- `orientation(int)`, `flip(string)`, `rotate(int)`\n\nFormat:\n- `format(string)` — `'jpg'`, `'png'`, `'webp'`, `'avif'`\n- `quality(int)` — 1-100\n\nOther:\n- `border(int, string, string)`, `watermark(string)`\n- `optimize()`, `nonOptimized()`\n\n### Conversion Configuration\n\n- `performOnCollections('col1', 'col2')` — limit to specific collections\n- `queued()` / `nonQueued()` — run async or sync\n- `withResponsiveImages()` — also generate responsive variants for this conversion\n- `keepOriginalImageFormat()` — preserve png/webp/gif instead of converting to jpg\n- `pdfPageNumber(int)` — which PDF page to render\n- `extractVideoFrameAtSecond(int)` — video thumbnail timing\n\n## Retrieving Media\n\n### Getting media items\n```php\n$media = $model->getMedia('images');                    // all in collection\n$first = $model->getFirstMedia('images');               // first item\n$last  = $model->getLastMedia('images');                // last item\n$has   = $model->hasMedia('images');                    // boolean check\n```\n\n### Getting URLs\n```php\n$url     = $model->getFirstMediaUrl('images');           // original URL\n$thumbUrl = $model->getFirstMediaUrl('images', 'thumb'); // conversion URL\n$lastUrl  = $model->getLastMediaUrl('images', 'thumb');\n```\n\n### Getting paths\n```php\n$path     = $model->getFirstMediaPath('images');\n$thumbPath = $model->getFirstMediaPath('images', 'thumb');\n```\n\n### Temporary URLs (S3)\n```php\n$tempUrl = $model->getFirstTemporaryUrl(\n    now()->addMinutes(30),\n    'images',\n    'thumb'\n);\n```\n\n### Fallback URLs\n```php\n$url = $model->getFallbackMediaUrl('avatar');\n```\n\n### From the Media model\n```php\n$media = $model->getFirstMedia('images');\n\n$media->getUrl();                    // original URL\n$media->getUrl('thumb');             // conversion URL\n$media->getPath();                   // disk path\n$media->getFullUrl();                // full URL with domain\n$media->getTemporaryUrl(now()->addMinutes(30));\n$media->hasGeneratedConversion('thumb');  // check if conversion exists\n```\n\n### Filtering media\n```php\n$media = $model->getMedia('images', function (Media $media) {\n    return $media->getCustomProperty('featured') === true;\n});\n\n$media = $model->getMedia('images', ['mime_type' => 'image/jpeg']);\n```\n\n## Custom Properties\n\nStore arbitrary metadata on media items:\n\n```php\n// When adding\n$model->addMedia($file)\n    ->withCustomProperties([\n        'alt' => 'Descriptive text',\n        'credits' => 'Photographer Name',\n    ])\n    ->toMediaCollection('images');\n\n// Get/set on existing media\n$media->setCustomProperty('alt', 'Updated text');\n$media->save();\n\n$alt = $media->getCustomProperty('alt');\n$has = $media->hasCustomProperty('alt');\n$media->forgetCustomProperty('alt');\n$media->save();\n```\n\n## Responsive Images\n\nGenerate multiple sizes for optimal loading:\n\n```php\n// On the FileAdder\n$model->addMedia($file)\n    ->withResponsiveImages()\n    ->toMediaCollection('images');\n\n// On a conversion\n$this->addMediaConversion('hero')\n    ->fit(Fit::Max, 1200, 800)\n    ->withResponsiveImages();\n\n// On a collection\n$this->addMediaCollection('photos')\n    ->withResponsiveImages();\n```\n\n### Using in Blade\n```blade\n{{-- Renders img tag with srcset --}}\n{{ $media->toHtml() }}\n\n{{-- With attributes --}}\n{{ $media->img()->attributes(['class' => 'w-full', 'alt' => 'Photo']) }}\n\n{{-- Get srcset string --}}\n<img src=\"{{ $media->getUrl() }}\" srcset=\"{{ $media->getSrcset() }}\" />\n\n{{-- Responsive conversion --}}\n<img src=\"{{ $media->getUrl('hero') }}\" srcset=\"{{ $media->getSrcset('hero') }}\" />\n```\n\n### Placeholder SVG\n```php\n$svg = $media->responsiveImages()->getPlaceholderSvg(); // tiny blurred base64 placeholder\n```\n\n## Managing Media\n\n### Clear a collection\n```php\n$model->clearMediaCollection('images');\n```\n\n### Clear except specific items\n```php\n$model->clearMediaCollectionExcept('images', $mediaToKeep);\n```\n\n### Delete specific media\n```php\n$model->deleteMedia($mediaId);\n```\n\n### Delete all media\n```php\n$model->deleteAllMedia();\n```\n\n### Delete model but keep media files\n```php\n$model->deletePreservingMedia();\n```\n\n### Reorder media\n```php\nMedia::setNewOrder([3, 1, 2]); // media IDs in desired order\n```\n\n### Move/copy media between models\n```php\n$media->move($otherModel, 'images');\n$media->copy($otherModel, 'images');\n```\n\n## Events\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Events\\MediaHasBeenAddedEvent;\nuse Spatie\\MediaLibrary\\Conversions\\Events\\ConversionWillStartEvent;\nuse Spatie\\MediaLibrary\\Conversions\\Events\\ConversionHasBeenCompletedEvent;\nuse Spatie\\MediaLibrary\\MediaCollections\\Events\\CollectionHasBeenClearedEvent;\n```\n\nListen to these events to hook into the media lifecycle:\n```php\nEvent::listen(MediaHasBeenAddedEvent::class, function ($event) {\n    $event->media; // the added Media model\n});\n\nEvent::listen(ConversionHasBeenCompletedEvent::class, function ($event) {\n    $event->media;\n    $event->conversion;\n});\n```\n\n## Configuration\n\nKey `config/media-library.php` options:\n\n```php\nreturn [\n    'disk_name' => 'public',                    // default disk\n    'max_file_size' => 1024 * 1024 * 10,         // 10MB\n    'queue_connection_name' => '',                // queue connection\n    'queue_name' => '',                          // queue name\n    'queue_conversions_by_default' => true,       // queue conversions\n    'media_model' => Spatie\\MediaLibrary\\MediaCollections\\Models\\Media::class,\n    'file_namer' => Spatie\\MediaLibrary\\Support\\FileNamer\\DefaultFileNamer::class,\n    'path_generator' => Spatie\\MediaLibrary\\Support\\PathGenerator\\DefaultPathGenerator::class,\n    'url_generator' => Spatie\\MediaLibrary\\Support\\UrlGenerator\\DefaultUrlGenerator::class,\n    'image_driver' => 'gd',                      // 'gd', 'imagick', or 'vips'\n    'image_optimizers' => [/* optimizer config */],\n    'version_urls' => true,                       // cache busting\n    'default_loading_attribute_value' => null,     // 'lazy' for lazy loading\n];\n```\n\n### Custom Path Generator\n\n```php\nuse Spatie\\MediaLibrary\\Support\\PathGenerator\\PathGenerator;\n\nclass CustomPathGenerator implements PathGenerator\n{\n    public function getPath(Media $media): string\n    {\n        return md5($media->id) . '/';\n    }\n\n    public function getPathForConversions(Media $media): string\n    {\n        return $this->getPath($media) . 'conversions/';\n    }\n\n    public function getPathForResponsiveImages(Media $media): string\n    {\n        return $this->getPath($media) . 'responsive/';\n    }\n}\n```\n\n### Custom File Namer\n\n```php\nuse Spatie\\MediaLibrary\\Support\\FileNamer\\FileNamer;\n\nclass CustomFileNamer extends FileNamer\n{\n    public function originalFileName(string $fileName): string\n    {\n        return Str::slug(pathinfo($fileName, PATHINFO_FILENAME));\n    }\n\n    public function conversionFileName(string $fileName, Conversion $conversion): string\n    {\n        return $this->originalFileName($fileName) . '-' . $conversion->getName();\n    }\n\n    public function responsiveFileName(string $fileName): string\n    {\n        return pathinfo($fileName, PATHINFO_FILENAME);\n    }\n}\n```\n\n### Custom Media Model\n\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media as BaseMedia;\n\nclass Media extends BaseMedia\n{\n    // Add custom methods, scopes, or override behavior\n}\n```\n\nRegister in config: `'media_model' => App\\Models\\Media::class`\n\n## Downloading Media\n\n### Single file\n```php\nreturn $media->toResponse($request); // download\nreturn $media->toInlineResponse($request); // display inline\nreturn $media->stream(); // stream\n```\n\n### ZIP download of collection\n```php\nuse Spatie\\MediaLibrary\\Support\\MediaStream;\n\nreturn MediaStream::create('photos.zip')\n    ->addMedia($model->getMedia('images'));\n```\n\n## Using with API Resources\n\n```php\nclass PostResource extends JsonResource\n{\n    public function toArray($request): array\n    {\n        return [\n            'id' => $this->id,\n            'title' => $this->title,\n            'image' => $this->getFirstMediaUrl('images'),\n            'thumb' => $this->getFirstMediaUrl('images', 'thumb'),\n            'media' => $this->getMedia('images')->map(function ($media) {\n                return [\n                    'id' => $media->id,\n                    'url' => $media->getUrl(),\n                    'thumb' => $media->getUrl('thumb'),\n                    'name' => $media->name,\n                    'size' => $media->size,\n                    'type' => $media->mime_type,\n                ];\n            }),\n        ];\n    }\n}\n```\n\n<!-- File: resources/boost/skills/medialibrary-development/SKILL.md (Tokens: ~820 | Category: anthropic-skill) -->\n---\nname: medialibrary-development\ndescription: Build and work with spatie/laravel-medialibrary features including associating files with Eloquent models, defining media collections and conversions, generating responsive images, and retrieving media URLs and paths.\nlicense: MIT\nmetadata:\n  author: Spatie\n---\n\n# Media Library Development\n\n## Overview\nUse spatie/laravel-medialibrary to associate files with Eloquent models. Supports image/video conversions, responsive images, multiple collections, and various storage disks.\n\n## When to Activate\n- Activate when working with file uploads, media attachments, or image processing in Laravel.\n- Activate when code references `HasMedia`, `InteractsWithMedia`, the `Media` model, or media collections/conversions.\n- Activate when the user wants to add, retrieve, convert, or manage files attached to Eloquent models.\n\n## Scope\n- In scope: media uploads, collections, conversions, responsive images, custom properties, file retrieval, path/URL generation.\n- Out of scope: general file storage without Eloquent association, non-Laravel frameworks.\n\n## Workflow\n1. Identify the task (model setup, adding media, defining conversions, retrieving files, etc.).\n2. Read `references/medialibrary-guide.md` and focus on the relevant section.\n3. Apply the patterns from the reference, keeping code minimal and Laravel-native.\n\n## Core Concepts\n\n### Model Setup\nEvery model that should have media must implement `HasMedia` and use the `InteractsWithMedia` trait:\n\n```php\nuse Spatie\\MediaLibrary\\HasMedia;\nuse Spatie\\MediaLibrary\\InteractsWithMedia;\n\nclass BlogPost extends Model implements HasMedia\n{\n    use InteractsWithMedia;\n}\n```\n\n### Adding Media\n```php\n$blogPost->addMedia($file)->toMediaCollection('images');\n$blogPost->addMediaFromUrl($url)->toMediaCollection('images');\n$blogPost->addMediaFromRequest('file')->toMediaCollection('images');\n```\n\n### Defining Collections\n```php\npublic function registerMediaCollections(): void\n{\n    $this->addMediaCollection('avatar')->singleFile();\n    $this->addMediaCollection('downloads')->useDisk('s3');\n}\n```\n\n### Defining Conversions\n```php\nuse Spatie\\MediaLibrary\\MediaCollections\\Models\\Media;\nuse Spatie\\Image\\Enums\\Fit;\n\npublic function registerMediaConversions(?Media $media = null): void\n{\n    $this->addMediaConversion('thumb')\n        ->fit(Fit::Contain, 300, 300)\n        ->nonQueued();\n}\n```\n\n### Retrieving Media\n```php\n$url = $model->getFirstMediaUrl('images');\n$thumbUrl = $model->getFirstMediaUrl('images', 'thumb');\n$allMedia = $model->getMedia('images');\n```\n\n## Do and Don't\n\nDo:\n- Always implement the `HasMedia` interface alongside the `InteractsWithMedia` trait.\n- Use `?Media $media = null` as the parameter for `registerMediaConversions()`.\n- Call `->toMediaCollection()` to finalize adding media.\n- Use `->nonQueued()` for conversions that should run synchronously.\n- Use `->singleFile()` on collections that should only hold one file.\n- Use `Spatie\\Image\\Enums\\Fit` enum values for fit methods.\n\nDon't:\n- Don't forget to run `php artisan vendor:publish --provider=\"Spatie\\MediaLibrary\\MediaLibraryServiceProvider\" --tag=\"medialibrary-migrations\"` before migrating.\n- Don't use `env()` for disk configuration; use `config()` or set it in `config/media-library.php`.\n- Don't call `addMedia()` without calling `toMediaCollection()` — the media won't be saved.\n- Don't reference conversion names that aren't registered in `registerMediaConversions()`.\n\n## References\n- `references/medialibrary-guide.md`\n</yakaai_skills>"}