PHPackages                             elegantly/laravel-media - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Image &amp; Media](/categories/media)
4. /
5. elegantly/laravel-media

ActiveLibrary[Image &amp; Media](/categories/media)

elegantly/laravel-media
=======================

A flexible media library for Laravel

v4.7.1(3mo ago)474.9k↓47.5%42MITPHPPHP ^8.1CI failing

Since Jan 17Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/ElegantEngineeringTech/laravel-media)[ Packagist](https://packagist.org/packages/elegantly/laravel-media)[ Docs](https://github.com/ElegantEngineeringTech/laravel-media)[ GitHub Sponsors](https://github.com/ElegantEngineeringTech)[ RSS](/packages/elegantly-laravel-media/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (16)Versions (142)Used By (2)

Extremely powerful media library for Laravel 🖼️
===============================================

[](#extremely-powerful-media-library-for-laravel-️)

[![Latest Version on Packagist](https://camo.githubusercontent.com/d5afbd283909bfd6fca39d841ffc04ff04f853ff9efd171eedbd9dd6c527914f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f656c6567616e746c792f6c61726176656c2d6d656469612e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/elegantly/laravel-media)[![GitHub Tests Action Status](https://camo.githubusercontent.com/9d3305eddc1557e15117ed97687a4677900535273dea69c84d0a3a1be3ed0731/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f456c6567616e74456e67696e656572696e67546563682f6c61726176656c2d6d656469612f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/ElegantEngineeringTech/laravel-media/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Coverage Action Status](https://camo.githubusercontent.com/308987cba68f4e5d1fde53b8e2a5c3306bf2e66c4adc5071c9a6157f028e1234/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f456c6567616e74456e67696e656572696e67546563682f6c61726176656c2d6d656469612f636f7665726167652e796d6c3f6272616e63683d6d61696e266c6162656c3d636f766572616765267374796c653d666c61742d737175617265)](https://github.com/ElegantEngineeringTech/laravel-media/actions?query=workflow%3Acoverage+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/7ff353ab5649adf51fcf1474ff2c4f913fff6ee2bbb49da6ab65171d97cecd3b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f456c6567616e74456e67696e656572696e67546563682f6c61726176656c2d6d656469612f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/ElegantEngineeringTech/laravel-media/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/90ffe1d12877a9aad0c73c3ddda3785e8323112598d97b791abba5794183fbca/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f656c6567616e746c792f6c61726176656c2d6d656469612e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/elegantly/laravel-media)

This package offers an extremely flexible media library, enabling you to store any type of file along with their conversions.

It provides advanced features such as:

- 🌐 Supports any filesystem solutions (local or cloud), such as S3, R2, Bunny.net, DO...
- ⚡ Supports any file conversion solutions (local or cloud), such as ffmpeg, Transloadit, Cloudflare, Coconut, and others.
- 🔄 Advanced nested media conversions
- 🚀 Rich metadata automatically extracted
- 🛠️ Highly flexible and customizable

I developed this package with the highest degree of flexibility possible and I have been using it in production for nearly two years, handling terabytes of files monthly.

Table of Contents
-----------------

[](#table-of-contents)

1. [Requirements](#requirements)
2. [Installation](#installation)
3. [Basic Usage](#basic-usage)

    - [Define Media Collection](#defining-media-collections)
    - [Define Media Conversions](#defining-media-conversions)
    - [Adding Media](#adding-media)
    - [Retreiving Media](#adding-media)
    - [Media properties](#media-properties)
    - [Accessing Media Conversions](#accessing-media-conversions)
    - [Blade components](#blade-components)
4. [Advanced Usage](#advanced-usage)

    - [Transforming a file before storing it](#transforming-a-file-before-storing-it)
    - [Async vs Sync conversions](#async-vs-sync-conversions)
    - [Delayed conversions](#delayed-conversions)
    - [`onAdded` MediaCollection Callback](#onadded-mediacollection-callback)
    - [`onCompleted` MediaConversionDefinition Callback](#oncompleted-mediaconversiondefinition-callback)
    - [Regenerating Child Conversions When a Parent Runs](#regenerating-child-conversions-when-a-parent-runs)
    - [Custom conversions](#custom-conversions)
    - [Manually generate conversions](#manually-generate-conversions)
    - [Format Media Url](#format-media-url)
5. [Conversions Presets](#conversions-presets)

    - [Image Placeholder](#image-placeholder)
6. [Customization](#customization)

    - [Custom Media Model](#custom-media-model)
7. [Troubleshooting](#troubleshooting)

    - [Ghostscript and Imagick Issues](#ghostscript-and-imagick-issues)

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 11.0+
- `spatie/image` for image conversions
- `spatie/pdf-to-image` for PDF to image conversions
- `ffmpeg` for video/audio processing

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require elegantly/laravel-media
```

You have to publish and run the migrations with:

```
php artisan vendor:publish --tag="media-migrations"
php artisan migrate
```

You can publish the config file with:

```
php artisan vendor:publish --tag="media-config"
```

This is the contents of the published config file:

```
use Elegantly\Media\Jobs\DeleteModelMediaJob;
use Elegantly\Media\Models\Media;
use Elegantly\Media\Models\MediaConversion;
use Elegantly\Media\PathGenerators\UuidPathGenerator;
use Elegantly\Media\UrlFormatters\DefaultUrlFormatter;

return [
    /**
     * The media model.
     * Define your own model here by extending \Elegantly\Media\Models\Media::class.
     */
    'model' => Media::class,

    /**
     * The MediaConversion model.
     * Define your own model here by extending \Elegantly\Media\Models\MediaConversion::class.
     */
    'media_conversion_model' => MediaConversion::class,

    /**
     * The path used to store temporary file copies for conversions.
     * This will be used with the storage_path() function.
     */
    'temporary_storage_path' => 'app/tmp/media',

    /**
     * The default disk used for storing files.
     */
    'disk' => env('MEDIA_DISK', env('FILESYSTEM_DISK', 'local')),

    /**
     * Determine if media should be deleted with the model
     * when using the HasMedia Trait.
     */
    'delete_media_with_model' => true,

    /**
     * Determine if media should be deleted with the model
     * when it is soft deleted.
     */
    'delete_media_with_trashed_model' => false,

    /**
     * Job class responsible for deleting media when the model is deleted.
     * This helps with performance and monitoring by queuing media deletions.
     */
    'delete_media_with_model_job' => DeleteModelMediaJob::class,

    /**
     * The default collection name assigned media.
     */
    'default_collection_name' => 'default',

    /**
     * The default URL formatter class.
     * Used when calling `$media->getUrl()`.
     */
    'default_url_formatter' => DefaultUrlFormatter::class,

    /**
     * The default path generator class.
     * Used when storing new files.
     */
    'default_path_generator' => UuidPathGenerator::class,

    /**
     * Prefix for the generated file path.
     * Set to null to disable the prefix.
     * Override the generateBasePath method in the Media model for full customization.
     */
    'generated_path_prefix' => null,

    /**
     * Queue connection name to use when dispatching media conversion jobs.
     */
    'queue_connection' => env('QUEUE_CONNECTION', 'sync'),

    /**
     * Queue name to use for media conversion jobs.
     * Set to null to use the default Laravel queue.
     */
    'queue' => null,

    /**
     * Configuration for FFmpeg processing.
     */
    'ffmpeg' => [
        /**
         * The binary path to the FFmpeg executable.
         */
        'ffmpeg_binaries' => env('FFMPEG_BINARIES', 'ffmpeg'),

        /**
         * The binary path to the FFprobe executable.
         */
        'ffprobe_binaries' => env('FFPROBE_BINARIES', 'ffprobe'),

        /**
         * Optional log channel for FFmpeg operations.
         * Set to null to disable logging.
         */
        'log_channel' => null,
    ],
];
```

Optionally, you can publish the views using

```
php artisan vendor:publish --tag="media-views"
```

Basic Usage
-----------

[](#basic-usage)

### Defining Media Collections

[](#defining-media-collections)

Media Collections define how media are stored, transformed, and processed for a specific model. They provide granular control over file handling, accepted types, and transformations.

To associate a media collection with a Model, start by adding the `InteractWithMedia` interface and the `HasMedia` trait.

Next, define your collections in the `registerMediaCollections` method, as shown below:

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Elegantly\Media\Concerns\HasMedia;
use Elegantly\Media\Contracts\InteractWithMedia;
use Elegantly\Media\MediaCollection;

class Channel extends Model implements InteractWithMedia
{
    use HasMedia;

    public function registerMediaCollections(): array;
    {
        return [
            new MediaCollection(
                name: 'avatar',
                single: true, // If true, only the latest file will be kept
                disk: 's3', // (optional) Specify where the file will be stored
                acceptedMimeTypes: [ // (optional) Specify accepted file types
                    'image/jpeg',
                    'image/png',
                    'image/webp'
                ]
            )
        ];
    }
}
```

### Defining Media Conversions

[](#defining-media-conversions)

Media conversions create different variants of your media files. For example, a 720p version of a 1440p video or a WebP or PNG version of an image are common types of media conversions. Interestingly, a media conversion can also have its own additional conversions.

This package provides common converter to simplify your work:

- `MediaImageConverter`: This converter optimizes, resizes, or converts any image using `spatie/image`.
- `MediaMp4Converter`: This conversion optimizes, resizes, or converts any video or gif to mp4 video using `ffmpeg`.
- `MediaWebmConverter`: This conversion optimizes, resizes, or converts any video or gif to webm video using `ffmpeg`.
- `MediaWavConverter`: This conversion optimizes, resizes, converts or extract any audio in wav format using `ffmpeg`.
- `MediaMp3Converter`: This conversion optimizes, resizes, converts or extract any audio in mp3 format using `ffmpeg`.
- `MediaFrameConverter`: This conversion extracts a frame from a video using `ffmpeg`.
- `MediaThumbnailConverter`: This conversion smartly extracts the best thumbnail from a video using `ffmpeg`.
- `MediaPdfToImageConverter`: This conversion extracts an image from the PDF using `spatie/pdf-to-image`.

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Elegantly\Media\Concerns\HasMedia;
use Elegantly\Media\Contracts\InteractWithMedia;
use Elegantly\Media\MediaCollection;
use Elegantly\Media\MediaConversionDefinition;
use Elegantly\Media\Converters\MediaMp4Converter;
use Elegantly\Media\Converters\MediaFrameConverter;
use Elegantly\Media\Converters\MediaImageConverter;

class Channel extends Model implements InteractWithMedia
{
    use HasMedia;

    public function registerMediaCollections(): array;
    {
        return [
            new MediaCollection(
                name: 'videos',
                conversions: [
                    new MediaConversionDefinition(
                        name: 'poster',
                        converter: fn ($media) => new MediaFrameConverter(
                            media: $media,
                            filename: "{$media->name}-thumbnail.jpg",
                            timecode: 0,
                        ),
                        conversions: [
                            new MediaConversionDefinition(
                                name: '360p',
                                converter: fn ($media) => new MediaImageConverter(
                                    media: $media,
                                    filename: "{$media->name}.jpg"
                                    width: 360
                                )
                            ),
                        ],
                    ),
                    new MediaConversionDefinition(
                        name: '720p',
                        converter: fn ($media) => new MediaMp4Converter(
                            media: $media,
                            filename: "{$media->name}.mp4"
                            width: 720
                        )
                    ),
                ]
            )
        ];
    }
}
```

### Adding Media

[](#adding-media)

Add media to your model, using the `addMedia` method, from various sources:

- an url
- a resource or stream
- a `\Illuminate\Http\UploadedFile` instance
- a `\Illuminate\Http\File` instance

#### From a Controller

[](#from-a-controller)

```
use Elegantly\Media\Exceptions\InvalidMimeTypeException;

public function store(Request $request, Channel $channel)
{
    try {
        $channel->addMedia(
            file: $request->file('avatar'),
            collectionName: 'avatar',
            name: "{$channel->name}-avatar"
        );
    } catch (InvalidMimeTypeException $exception){
        // Will throw an error if the mime type is not included in the collection's `acceptedMimeTypes` parameter.
    }
}
```

#### From a Livewire Component

[](#from-a-livewire-component)

```
use Livewire\WithFileUploads;
use Elegantly\Media\Exceptions\InvalidMimeTypeException;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;

class ImageUploader extends Component
{
    use WithFileUploads;

    /** @var ?TemporaryUploadedFile */
    public $avatar = null;

    public function save()
    {
        try {
            $this->channel->addMedia(
                file: $this->avatar->getRealPath(),
                collectionName: 'avatar',
                name: "{$this->channel->name}-avatar"
            );
        } catch (InvalidMimeTypeException $exception){
            // Will throw an error if the mime type is not included in the collection's `acceptedMimeTypes` parameter.
        }
    }
}
```

#### From an Url

[](#from-an-url)

```
use Elegantly\Media\Exceptions\InvalidMimeTypeException;

 try {
    $channel->addMedia(
        file: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
        collectionName: 'videos',
        name: "BigBuckBunny"
    );
} catch (InvalidMimeTypeException $exception){
    // Will throw an error if the mime type is not included in the collection's `acceptedMimeTypes` parameter.
}
```

### Retreiving Media

[](#retreiving-media)

Retrieve media from your model:

```
// Get all media from a specific collection
$avatars = $channel->getMedia('avatar');

// Get the first media from a collection
$avatar = $channel->getFirstMedia('avatar');

// Check if media exists
$hasAvatar = $channel->hasMedia('avatar');
```

### Media properties

[](#media-properties)

Each media item provides rich metadata automatically:

```
$media = $channel->getFirstMedia('avatar');

// File properties
$media->name; // file_name without the extension
$media->file_name;
$media->extension;
$media->mime_type;
$media->size; // in bytes
$media->humanReadableSize();

// Image/Video specific properties
$media->width;       // in pixels
$media->height;      // in pixels
$media->aspect_ratio;
$media->duration;    // for video/audio
```

You can use dot notation to access either the root properties or a specific conversion:

```
// Get the original media URL
$originalUrl = $media->getUrl();

// Get a specific conversion URL
$thumbnailUrl = $media->getUrl(
    conversion: '360p',
    fallback: true // Falls back to original if conversion doesn't exist
);

$posterUrl = $media->getUrl(
    conversion: 'poster.360p',
    fallback: 'poster' // Falls back to an other conversion if conversion doesn't exist
);

// Use the same logic with other properties such as
$media->getPath();
$media->getWidth();
// ...
```

### Access Media Conversions

[](#access-media-conversions)

To directly access conversions, use:

```
// Check if a conversion exists
$hasThumbnail = $media->hasConversion('100p');

// Get a specific conversion
$thumbnailConversion = $media->getConversion('100p');

// Get the 'poster' conversion
$media->getParentConversion('poster.360p');

// Only get children conversions of poster
$media->getChildrenConversions('poster');
```

### Blade components

[](#blade-components)

The package also provides blade components.

```

```

```

```

Advanced Usage
--------------

[](#advanced-usage)

### Transforming a file before storing it

[](#transforming-a-file-before-storing-it)

You can define a transformation step that runs before a file is stored by using the `transform` method on a media collection.

This is useful if you want to **resize, optimize, or process an uploaded file** before it gets saved to disk.

For example, the snippet below shows how you can resize and optimize an uploaded image before storing it:

```
use Illuminate\Http\File;
use Spatie\TemporaryDirectory\TemporaryDirectory;
use Spatie\Image\Image;
use Spatie\Image\Enums\Fit;

new MediaCollection(
    name: 'avatar',
    acceptedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
    transform: function (File $file, TemporaryDirectory $temporaryDirectory): File {
        $input = $file->getRealPath();
        $output = $temporaryDirectory->path('avatar.jpg');

        Image::load($input)
            ->fit(Fit::Contain, 500)
            ->optimize()
            ->save($output);

        return new File($output);
    }
);
```

### Async vs. Sync Conversions

[](#async-vs-sync-conversions)

When adding new media, its conversions can be either dispatched asynchronously or generated synchronously.

You can configure the strategy in the conversion definition using the `queued` and `queue` parameters:

```
new MediaCollection(
    name: 'avatar',
    conversions: [
        new MediaConversionDefinition(
            name: '360p',
            queued: true,  // (default) Dispatch as a background job
            queue: 'slow' // (optional) Specify a custom queue
            converter: fn ($media) => new MediaImageConverter(
                media: $media,
                filename: "{$media->name}.jpg"
                width: 360
            )
        ),
        new MediaConversionDefinition(
            name: '180p',
            queued: false,  // Generate the conversion synchronously
            converter: fn ($media) => new MediaImageConverter(
                media: $media,
                filename: "{$media->name}.jpg"
                width: 180
            )
        ),
    ]
)
```

Synchronous conversions can be particularly useful in specific use cases, such as generating a poster immediately upon upload.

### Delayed Conversions

[](#delayed-conversions)

There are scenarios where you might want to define conversions that should not be generated immediately. For instance, if a conversion is resource-intensive or not always required, you can defer its generation to a later time.

To achieve this, configure the conversion with the `immediate` parameter set to `false`. This allows you to generate the conversion manually when needed:

```
new MediaCollection(
    name: 'avatar',
    conversions: [
        new MediaConversionDefinition(
            name: '360p',
            immediate: false, // Conversion will not be generated at upload time
            converter: fn ($media) => new MediaImageConverter(
                media: $media,
                filename: "{$media->name}.jpg"
                width: 360
            )
        ),
    ]
)
```

To generate the conversion later, you can use the following methods:

```
// Generate the conversion synchronously
$media->executeConversion(
    conversion: '360',
    force: false, // Skips execution if the conversion already exists
    withChildren: true, // Generate children conversion
    withForceChildren: true, // Force children conversion to be re-generated
);

// Dispatch the conversion as a background job
$media->dispatchConversion(
    conversion: '360',
    force: false, // Skips execution if the conversion already exists
    withChildren: true, // Generate children conversion
    withForceChildren: true, // Force children conversion to be re-generated
);
```

### `onAdded` MediaCollection Callback

[](#onadded-mediacollection-callback)

The `onAdded` callback allows you to define custom logic that will be executed whenever new media is added to your collection.

To use it, simply set the `onAdded` parameter when defining a `MediaCollection`. For example:

```
new MediaCollection(
    name: 'avatar',
    onAdded: function ($media) {
        // Example: Notify the model when new media is added
        // $media->model->notify(new MediaAddedNotification($media));
    }
);
```

With this, you can easily hook into the media addition process and trigger actions like sending notifications, logging, or other custom behavior.

Tip

The same behavior can be achieved by listening to `Elegantly\Media\Events\MediaAddedEvent`.

### `onCompleted` MediaConversionDefinition Callback

[](#oncompleted-mediaconversiondefinition-callback)

The `onCompleted` callback allows you to define custom logic that will be executed whenever a new conversion is generated.

To use it, simply set the `onCompleted` parameter when defining a `MediaConversionDefinition`. For example:

```
new MediaConversionDefinition(
    name: '360',
    onCompleted: function ($conversion, $media, $parent) {
        // Example: Refresh your UI
        // broadcast(new MyEvent($media));
    }
);
```

This allows you to hook into the conversion process and execute additional logic, such as updating your UI or triggering other actions.

Tip

The same behavior can be achieved by listening to `Elegantly\Media\Events\MediaConversionAddedEvent`.

### Regenerating Child Conversions When a Parent Runs

[](#regenerating-child-conversions-when-a-parent-runs)

In some cases, you may want all **child conversions** of a given conversion to be **regenerated whenever the parent conversion is re-executed**.

There are two ways to achieve this, depending on where you handle conversions:

#### From the conversion definition

[](#from-the-conversion-definition)

You can hook into the `onCompleted` callback of a conversion definition and instruct Laravel to delete all child conversions when the parent finishes.

```
new MediaConversionDefinition(
    name: '360',
    onCompleted: function (?MediaConversion $conversion, Media $media, ?MediaConversion $parent) {
        if ($conversion) {
            // Delete any children so they’ll be regenerated automatically
            $media->deleteChildrenConversions($conversion->conversion_name);
        }
    }
);
```

#### From `addConversion`

[](#from-addconversion)

When adding a new conversion programmatically, you can explicitly tell it to remove children and regenerate them automatically:

```
$mediaConversion = $media->addConversion(
    file: $file,
    deleteChildren: true, // ensures child conversions are refreshed
);

// regenerate children conversion
$this->media->generateConversions(
    parent: $mediaConversion,
);
```

### Custom Conversions

[](#custom-conversions)

Conversions can be anything: a variant of a file, a transcription of a video, a completely new file, or even just a string.

You can use built-in presets or define your own custom converter. To create a custom converter, start by creating a new class extending the `MediaConverter` class:

```
namespace App\Media\Converters\Image;

use Elegantly\Media\Converters\MediaConverter;
use Elegantly\Media\Enums\MediaType;
use Elegantly\Media\Models\Media;
use Elegantly\Media\Models\MediaConversion;
use Illuminate\Contracts\Filesystem\Filesystem;
use Spatie\Image\Enums\Fit;
use Spatie\Image\Image;
use Spatie\ImageOptimizer\OptimizerChain;
use Spatie\TemporaryDirectory\TemporaryDirectory as SpatieTemporaryDirectory;

class MediaImageConverter extends MediaConverter
{
    public function __construct(
        public readonly Media $media,
        public string $filename,
        public ?int $width = null,
        public ?int $height = null,
        public Fit $fit = Fit::Max,
        public ?OptimizerChain $optimizerChain = null,
    ) {}

    public function shouldExecute(Media $media, ?MediaConversion $parent): bool
    {
        $source = $parent ?? $media;

        return $source->type === MediaType::Image;
    }

    public function convert(
        Media $media,
        ?MediaConversion $parent,
        ?string $file,
        Filesystem $filesystem,
        SpatieTemporaryDirectory $temporaryDirectory
    ): ?MediaConversion {

        if (! $file) {
            return null;
        }

        $input = $filesystem->path($file);
        $output = $filesystem->path($this->filename);

        Image::load($input)
            ->fit($this->fit, $this->width, $this->height)
            ->optimize($this->optimizerChain)
            ->save($output);

        return $media->addConversion(
            file: $output,
            conversionName: $this->conversion,
            parent: $parent,
        );

    }
}
```

The `convert` method is where the logic for the conversion is implemented. It provides the following parameters:

- **`$media`**: The Media model.
- **`$parent`**: The MediaConversion model, if the conversion is nested.
- **`$file`**: A local copy of the file associated with either `$media` or `$parent`.
- **`$filesystem`**: An instance of the local filesystem where the file copy is stored.
- **`$temporaryDirectory`**: An instance of `TemporaryDirectory` where the file copy is temporarily stored.

You don’t need to worry about cleaning up the files, as the `$temporaryDirectory` will be deleted automatically when the process completes or fails.

To finalize the conversion, ensure you save it by calling `$media->addConversion` or `$media->replaceConversion` at the end of the `handle` method.

### Manually Generate Conversions

[](#manually-generate-conversions)

You can manage your media conversions programmatically using the following methods:

```
// Store a new file as a conversion
$media->addConversion(
    file: $file, // Can be an HTTP File, URL, or file path
    conversionName: 'transcript',
    parent: $mediaConversion // (Optional) Specify a parent conversion
    // Additional parameters...
);

// Replace an existing conversion safely
// If the same conversion already exists, it ensures the new file is stored before deleting the previous one.
$media->replaceConversion(
    conversion: $mediaConversion
);

// Safely delete a specific conversion and all its children
$media->deleteConversion('360');

// Safely delete only the child conversions of a parent conversion
$media->deleteChildrenConversions('poster');

// Dispatch or execute a conversion
$media->dispatchConversion('360'); // Runs asynchronously as a job
$media->executeConversion('poster.360'); // Executes synchronously
$media->getOrExecuteConversion('poster.360'); // Retrieves or generates the conversion

// Retrieve conversion information
$media->getConversion('360'); // Fetch a specific conversion
$media->hasConversion('360'); // Check if a conversion exists
$media->getParentConversion('poster.360'); // Retrieve the parent (poster) of a conversion
$media->getChildrenConversions('poster'); // Retrieve child conversions
```

Additionally, you can use an Artisan command to generate conversions with various options:

```
php artisan media:generate-conversions
```

You can also use the following command to retry failed conversions:

```
php artisan media-conversions:retry
```

This provides a convenient way to process conversions in bulk or automate them within your workflows.

### Format Media URLs

[](#format-media-urls)

Some cloud providers like Cloudflare, Bunny, or ImageKit allow you to create instant transformations of your images and videos using specially formatted URLs.

This package gives you a simple way to format your URLs so you can take advantage of these services.

When using the `$media->getUrl()` method, you can specify two parameters:

- `parameters`: An array of values
- `formatter`: The class name of the formatter you want to use

By combining these parameters, you can retrieve formatted URLs like this:

```
use \Elegantly\Media\UrlFormatters\CloudflareImageUrlFormatter;

// Default formatter (query parameters)
$default = $media->getUrl(
    parameters: ['width' => 360],
); // https://your-url.com?width=360

// Cloudflare formatter (path-based format)
$cloudflare = $media->getUrl(
    parameters: ['width' => 360],
    formatter: CloudflareImageUrlFormatter::class
); // /cdn-cgi/media/width=360/https://your-url.com
```

This package comes with 3 formatters out of the box:

- `\Elegantly\Media\UrlFormatters\DefaultUrlFormatter`
- `\Elegantly\Media\UrlFormatters\CloudflareImageUrlFormatter`
- `\Elegantly\Media\UrlFormatters\CloudflareVideoUrlFormatter`

Feel free to implement your own formatter by extending `\Elegantly\Media\UrlFormatters\AbstractUrlFormatter`.

Conversions Presets
-------------------

[](#conversions-presets)

### Image Placeholder

[](#image-placeholder)

When loading images on the web, it’s common to show a low-resolution blurred placeholder first, then swap it with the full-resolution image once it’s ready. This improves perceived performance, reduces layout shifts, and gives a polished feel to your UI.

This package includes a ready-to-use preset for generating these placeholders in Base64-encoded format. The placeholder is tiny in size, quick to load, and designed to be displayed while the actual image is being fetched.

#### Defining the Placeholder Conversion

[](#defining-the-placeholder-conversion)

To create a placeholder, simply define a media conversion using the `MediaImagePlaceholderConverter`:

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Elegantly\Media\Concerns\HasMedia;
use Elegantly\Media\Contracts\InteractWithMedia;
use Elegantly\Media\MediaCollection;
use Elegantly\Media\MediaConversionDefinition;
use Elegantly\Media\Converters\MediaImagePlaceholderConverter;

class User extends Model implements InteractWithMedia
{
    use HasMedia;

    public function registerMediaCollections(): array;
    {
        return [
            new MediaCollection(
                name: 'avatar',
                single: true,
                conversions: [
                    new MediaConversionDefinition(
                        name: 'placeholder',
                        converter: fn ($media) => new MediaImagePlaceholderConverter($media),
                    ),
                ]
            )
        ];
    }
}
```

#### Using the Placeholder in Your Views

[](#using-the-placeholder-in-your-views)

If you’re using the built-in x-media::img Blade component, you can directly specify the placeholder conversion name:

```

```

This will automatically load the blurred placeholder as a background-image.

#### Retrieving the Base64 Value Directly

[](#retrieving-the-base64-value-directly)

If you need to manually work with the placeholder (e.g., for API responses or inline styles), you can retrieve it like this:

```
$placeholder = $media->getConversion('placeholder');

$base64Image = $placeholder?->contents;
```

Customization
-------------

[](#customization)

### Custom Media Model

[](#custom-media-model)

You can define your own Media model to use with the library.

First, create your own model class:

```
namespace App\Models;

use Elegantly\Media\Models\Media as ElegantlyMedia;

class Media extends ElegantlyMedia
{
    // ...
}
```

Then, update the `config` file:

```
use App\Models\Media;

return [

    'model' => Media::class,

    // ...

];
```

The library is typed with generics, so you can use your own Media model seamlessly:

```
namespace App\Models;

use App\Models\Media;
use Elegantly\Media\Concerns\HasMedia;
use Elegantly\Media\Contracts\InteractWithMedia;

/**
 * @implements InteractWithMedia
 */
class Post extends Model implements InteractWithMedia
{
    /** @use HasMedia **/
    use HasMedia;

    // ...
}
```

Troubleshooting
---------------

[](#troubleshooting)

### Ghostscript and Imagick Issues

[](#ghostscript-and-imagick-issues)

This package relies on the `spatie/pdf-to-image` library, which uses Ghostscript via Imagick to convert PDFs into images.

If you encounter errors while generating images from PDFs, such as:

- `attempt to perform an operation not allowed by the security policy 'PDF'`
- `Uncaught ImagickException: FailedToExecuteCommand 'gs'`

these issues are likely related to the configuration of Ghostscript or Imagick on your system.

For detailed guidance on resolving these errors, refer to the [spatie/pdf-to-image documentation on Ghostscript issues](https://github.com/spatie/pdf-to-image/blob/main/README.md#issues-regarding-ghostscript).

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see the [CHANGELOG](CHANGELOG.md) for more information on recent changes.

Contributing
------------

[](#contributing)

Feel free to open an issue or a discussion.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please contact [me](https://github.com/QuentinGab) to report security vulnerabilities.

Credits
-------

[](#credits)

- [Quentin Gabriele](https://github.com/QuentinGab)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see the [License File](LICENSE.md) for more information.

###  Health Score

55

—

FairBetter than 98% of packages

Maintenance87

Actively maintained with recent releases

Popularity35

Limited adoption so far

Community17

Small or concentrated contributor base

Maturity68

Established project with proven stability

 Bus Factor1

Top contributor holds 92.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

140

Last Release

97d ago

Major Versions

v0.0.3 → v1.0.02024-01-21

v1.2.2 → v2.0.02024-05-23

v2.2.8 → v3.0.02024-10-13

v3.7.2 → v4.0.02025-08-14

### Community

Maintainers

![](https://www.gravatar.com/avatar/d769563728757858e249f266ac2555f28db5640124df92821ed17d93ee76fc7b?d=identicon)[QuentinGab](/maintainers/QuentinGab)

---

Top Contributors

[![QuentinGab](https://avatars.githubusercontent.com/u/40128136?v=4)](https://github.com/QuentinGab "QuentinGab (395 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (20 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (10 commits)")

---

Tags

imageslaravelmediaphpuploadvideoslaravelElegantlylaravel-media

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/elegantly-laravel-media/health.svg)

```
[![Health](https://phpackages.com/badges/elegantly-laravel-media/health.svg)](https://phpackages.com/packages/elegantly-laravel-media)
```

###  Alternatives

[finller/laravel-media

A flexible media library for Laravel

472.1k](/packages/finller-laravel-media)[saasykit/laravel-open-graphy

An awesome open graph image (social cards) generator package for Laravel.

13057.0k](/packages/saasykit-laravel-open-graphy)[ace-of-aces/laravel-image-transform-url

Easy, URL-based image transformations inspired by Cloudflare Images.

1756.4k](/packages/ace-of-aces-laravel-image-transform-url)[webplusm/gallery-json-media

a filament media storing in a Json field

196.0k](/packages/webplusm-gallery-json-media)[johncarter/filament-focal-point-picker

An image focal point picker for Filament Admin.

4326.5k1](/packages/johncarter-filament-focal-point-picker)[ralphjsmit/laravel-glide

Auto-magically generate responsive images from static image files.

4719.6k5](/packages/ralphjsmit-laravel-glide)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
