> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lunarphp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Media

> Managing images and files on Lunar models using Spatie's Media Library.

Lunar uses Spatie's Media Library package to manage images and files across models.

## Overview

Lunar uses the [Laravel-medialibrary](https://spatie.be/docs/laravel-medialibrary) package by Spatie to handle media across all models. Rather than reinvent the wheel, Lunar leverages this battle-tested package and adds its own conventions on top, including per-model media definition classes, automatic image conversions, and primary image management.

### Supported Models

The following models support media out of the box via the `Lunar\Core\Models\Concerns\HasMedia` trait:

| Model                | Class                                  |
| -------------------- | -------------------------------------- |
| Product              | `Lunar\Core\Models\Product`            |
| Product Type         | `Lunar\Core\Models\ProductType`        |
| Product Option       | `Lunar\Core\Models\ProductOption`      |
| Product Option Value | `Lunar\Core\Models\ProductOptionValue` |
| Collection           | `Lunar\Core\Models\Collection`         |
| Brand                | `Lunar\Core\Models\Brand`              |
| Asset                | `Lunar\Core\Models\Asset`              |

<Info>
  `Lunar\Core\Models\ProductVariant` does not use the `HasMedia` trait. Instead, it uses a many-to-many relationship with media through a pivot table. See [Product Variant Images](#product-variant-images) for details.
</Info>

## Configuration

The media configuration is published at `config/lunar/media.php`.

```php theme={null}
use Lunar\Core\Media\StandardDefinitions;

return [
    'definitions' => [
        'asset' => StandardDefinitions::class,
        'brand' => StandardDefinitions::class,
        'collection' => StandardDefinitions::class,
        'product' => StandardDefinitions::class,
        'product-option' => StandardDefinitions::class,
        'product-option-value' => StandardDefinitions::class,
        'product_type' => StandardDefinitions::class,
    ],

    'collection' => 'images',

    'max_upload_kb' => 8192,

    'fallback' => [
        'url' => env('FALLBACK_IMAGE_URL', null),
        'path' => env('FALLBACK_IMAGE_PATH', null),
    ],
];
```

| Key             | Description                                                                                                                             |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `definitions`   | Maps a model alias to its media definition class. Each model can have its own definition class to customize collections and conversions |
| `collection`    | The default media collection name used across all models                                                                                |
| `max_upload_kb` | Maximum accepted upload size per image, in kilobytes                                                                                    |
| `fallback.url`  | A fallback URL returned when a model has no media                                                                                       |
| `fallback.path` | A fallback file path returned when a model has no media                                                                                 |

The `definitions` keys use aliases derived from the model class name (a snake-cased basename, e.g. `Product` becomes `product`, `ProductOptionValue` becomes `product-option-value`, `ProductType` becomes `product_type`). The fully qualified class name can also be used as the key.

## Adding Media

Adding media to a model follows the standard Spatie Media Library API.

```php theme={null}
use Lunar\Core\Models\Product;

$product = Product::find(123);

$product->addMedia($request->file('image'))->toMediaCollection('images');
```

For more information, see [Associating files](https://spatie.be/docs/laravel-medialibrary/v11/basic-usage/associating-files) in the Spatie documentation.

## Retrieving Media

```php theme={null}
use Lunar\Core\Models\Product;

$product = Product::find(123);

// Get all images
$product->getMedia('images');

// Get the first image URL
$product->getFirstMediaUrl('images');

// Get a specific conversion URL
$product->getFirstMediaUrl('images', 'medium');
```

For more information, see [Retrieving media](https://spatie.be/docs/laravel-medialibrary/v11/basic-usage/retrieving-media) in the Spatie documentation.

## Primary Images

Lunar adds a concept of "primary" images on top of Spatie's media library. Each model can have one image marked as primary per collection, tracked via a `primary` custom property on the media item.

### Thumbnail Relationship

Models using the `HasMedia` trait have a `thumbnail` relationship that returns the primary image, scoped to the configured media collection (`config('lunar.media.collection')`, `images` by default):

```php theme={null}
use Lunar\Core\Models\Product;

$product = Product::find(123);

// Get the primary image via the thumbnail relationship
$product->thumbnail;
```

### Automatic Primary Management

Lunar includes a `Lunar\Core\Observers\MediaObserver` that automatically enforces the following rules for the configured media collection:

* When a media item is marked as primary, all other items in the same collection are unmarked.
* When the primary image is deleted, the first remaining image in the collection is automatically promoted to primary.
* When a new image is added to an empty collection, it is automatically marked as primary.

### Setting the Primary Image

```php theme={null}
$media = $product->getMedia('images')->first();

$media->setCustomProperty('primary', true)->save();
```

## Product Variant Images

`Lunar\Core\Models\ProductVariant` handles media differently from other models. Instead of using the `HasMedia` trait, it uses a many-to-many relationship with the `media` table through a `media_product_variant` pivot table.

```php theme={null}
use Lunar\Core\Models\ProductVariant;

$variant = ProductVariant::find(456);

// Get all images (ordered by position)
$variant->images;

// Get the thumbnail: the pivot-primary image, or the parent product's thumbnail
$variant->getThumbnail();
```

### Relationships

| Relationship | Type          | Related Model                                       | Description                                                 |
| :----------- | :------------ | :-------------------------------------------------- | :---------------------------------------------------------- |
| `images`     | BelongsToMany | `Spatie\MediaLibrary\MediaCollections\Models\Media` | Images assigned to the variant, ordered by pivot `position` |

### Pivot columns (`media_product_variant`)

| Column     | Type           | Description                                                        |
| ---------- | -------------- | ------------------------------------------------------------------ |
| `primary`  | `boolean`      | Whether this is the primary image for the variant, default `false` |
| `position` | `smallInteger` | Display order of the image, default `1`                            |

`getThumbnail()` returns the image whose pivot `primary` flag is set; if none is marked, it falls back to the parent product's `thumbnail`.

## Fallback Images

If a model has no media, calling `getFirstMediaUrl` or `getFirstMediaPath` returns an empty string by default. Fallback images can be configured in `config/lunar/media.php` or via environment variables:

```php theme={null}
'fallback' => [
    'url' => env('FALLBACK_IMAGE_URL', null),
    'path' => env('FALLBACK_IMAGE_PATH', null),
]
```

These values are passed to Spatie's `useFallbackUrl` and `useFallbackPath` methods on the media collection.

## Default Conversions

`Lunar\Core\Media\StandardDefinitions`, the default media definition class, registers the following image conversions:

| Conversion | Width | Height | Notes                                                                          |
| ---------- | ----- | ------ | ------------------------------------------------------------------------------ |
| `small`    | 300   | 300    | Registered globally (runs for all collections). Used by admin panel thumbnails |
| `medium`   | 500   | 500    | Registered on the configured media collection                                  |
| `large`    | 800   | 800    | Registered on the configured media collection                                  |
| `zoom`     | 500   | 500    | Registered on the configured media collection                                  |

All conversions use `Spatie\Image\Enums\Fit::Fill` for sizing, a white background and border, and preserve the original image format.

## Custom Media Definitions

To customize the media collections and conversions for a model, create a class that implements `Lunar\Core\Contracts\MediaDefinitions`:

```php theme={null}
namespace App\Media;

use Lunar\Core\Contracts\MediaDefinitions;
use Spatie\Image\Enums\Fit;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\MediaCollections\MediaCollection;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class CustomMediaDefinitions implements MediaDefinitions
{
    public function registerMediaConversions(HasMedia $model, ?Media $media = null): void
    {
        $model->addMediaConversion('small')
            ->fit(Fit::Fill, 300, 300)
            ->sharpen(10)
            ->keepOriginalImageFormat();
    }

    public function registerMediaCollections(HasMedia $model): void
    {
        $fallbackUrl = config('lunar.media.fallback.url');
        $fallbackPath = config('lunar.media.fallback.path');

        // Reset to avoid duplication
        $model->mediaCollections = [];

        $collection = $model->addMediaCollection(config('lunar.media.collection'));

        if ($fallbackUrl) {
            $collection = $collection->useFallbackUrl($fallbackUrl);
        }

        if ($fallbackPath) {
            $collection = $collection->useFallbackPath($fallbackPath);
        }

        $this->registerCollectionConversions($collection, $model);
    }

    protected function registerCollectionConversions(MediaCollection $collection, HasMedia $model): void
    {
        $conversions = [
            'zoom' => ['width' => 500, 'height' => 500],
            'large' => ['width' => 800, 'height' => 800],
            'medium' => ['width' => 500, 'height' => 500],
        ];

        $collection->registerMediaConversions(function (Media $media) use ($model, $conversions) {
            foreach ($conversions as $key => $conversion) {
                $model->addMediaConversion($key)
                    ->fit(Fit::Fill, $conversion['width'], $conversion['height'])
                    ->keepOriginalImageFormat();
            }
        });
    }

    public function getMediaCollectionTitles(): array
    {
        return ['images' => 'Images'];
    }

    public function getMediaCollectionDescriptions(): array
    {
        return ['images' => ''];
    }
}
```

Then register the class in `config/lunar/media.php`:

```php theme={null}
'definitions' => [
    'product' => \App\Media\CustomMediaDefinitions::class,
    // ...
],
```

### Regenerating Conversions

After changing conversion definitions, regenerate existing conversions using the Spatie artisan command:

```bash theme={null}
php artisan media-library:regenerate
```

This creates queue jobs for each media entry to be reprocessed. See the [Spatie documentation](https://spatie.be/docs/laravel-medialibrary/v11/converting-images/regenerating-images) for more options.

## Extending Your Own Models

Custom models can be given media support using the Lunar `HasMedia` trait:

```php theme={null}
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Lunar\Core\Models\Concerns\HasMedia;
use Spatie\MediaLibrary\HasMedia as SpatieHasMedia;

class YourCustomModel extends Model implements SpatieHasMedia
{
    use HasMedia;
}
```

This provides access to all Lunar media features, including per-model media definitions and automatic conversions.

<Warning>
  To use Lunar's media definitions and conversions, models must use the `Lunar\Core\Models\Concerns\HasMedia` trait. Using Spatie's `InteractsWithMedia` trait directly bypasses Lunar's media definition system. See the [Spatie documentation](https://spatie.be/docs/laravel-medialibrary/v11/basic-usage/preparing-your-model) for using the library directly.
</Warning>

### Definition Resolution

The `HasMedia` trait resolves the media definition class for a model using the following priority:

1. A snake\_case alias in `config('lunar.media.definitions')` (e.g. `product` for `Product`)
2. The fully qualified class name in the config
3. The parent class name in the config (useful for extended models)
4. Falls back to `Lunar\Core\Media\StandardDefinitions`
