Search by

donia-shaker / media-library

donia-shaker

Package info

github.com/donia-shaker/media-library

pkg:composer/donia-shaker/media-library

Statistics

Installs: 2 854

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v4.0.0 2026-10-18 07:22 UTC

This package is auto-updated.

Last update: 2026-10-18 07:23:59 UTC


README

This package provides a set of functions for handling media files, including images, audio, video, PDF files, and other documents.

Features

  • Public and Private Storage: Store media in publicly accessible storage or protected private storage.
  • Image Conversion: Convert and store images in multiple formats.
  • Automatic Thumbnail Generation: Automatically generate thumbnails when enabled.
  • Universal File Storage: Store images, documents, PDFs, videos, audio files, and other file types.
  • Soft Delete Support: Restore accidentally deleted media records when needed.
  • Public and Protected URL Generation: Generate direct URLs for public media and signed authenticated URLs for private media.
  • Image Upload Rules: Apply customizable dimensions, size, and format validation rules.
  • Authentication Support: Protect private media using configurable Laravel authentication guards.
  • Frontend Independent: Private media can be consumed from Vue, React, Angular, mobile applications, or any client capable of sending authenticated HTTP requests.

Installation

To install the DoniaShaker\MediaLibrary package, follow these steps.

1. Install the package

composer require donia-shaker/media-library

2. Publish migrations and config

php artisan vendor:publish --tag=media-library-migrations
php artisan vendor:publish --tag=media-library-config

3. Configure the default image format

Edit:

config/media.php

Example:

'default_image_format' => 'webp',

4. Configure environment variables

Required: Set APP_URL in .env to your project's actual base URL, including http:// or https://. The library uses this value to generate media URLs, so an incorrect value will produce incorrect file links.

APP_URL=https://your-project.com
MEDIA_USE_STORAGE=true
MEDIA_CREATE_THUMBNAILS=true

For local development, use your project's local URL, for example APP_URL=http://localhost/my-project. Do not include /media or /storage/media; the library adds the media path automatically.

Private media uses the sanctum authentication guard by default.

If your project uses another authentication guard, such as JWT with an api guard:

MEDIA_PRIVATE_AUTH_GUARD=api

5. Run migrations

php artisan migrate

Usage

Initialization

Import the controller:

use DoniaShaker\MediaLibrary\MediaController;

Create an instance:

$media_controller = new MediaController();

The $format parameter can be null. In this case, the package uses the configured default_image_format.

Save Image

Saves an image, optionally resizes it, controls image quality, and generates a thumbnail when enabled.

$media_controller->saveImage($model, $model_id, $file, $format, $maxWidth, $maxHeight, $quality);

$format, $maxWidth, $maxHeight, and $quality are optional.

Queued Image Processing

Existing installations keep processing images immediately. Queued processing is optional and does not require changes to calls to saveImage() or convertTempImage(), or new media migrations.

Enable it in .env:

MEDIA_QUEUE_IMAGES=true
QUEUE_CONNECTION=database

The library uses the application's connection from config/queue.php, including database, redis, and other Laravel queue connections. Prepare that connection as usual (for example, the jobs table for database, or a configured Redis connection). It does not create or replace the project's queue configuration.

You can optionally select a different connection and queue name in config/media.php:

'queueImages' => env('MEDIA_QUEUE_IMAGES', false),
'queueConnection' => env('MEDIA_QUEUE_CONNECTION'), // null: use queue.default
'queueName' => env('MEDIA_QUEUE_NAME'), // null: use the connection's queue

For example:

MEDIA_QUEUE_IMAGES=true
MEDIA_QUEUE_CONNECTION=redis
MEDIA_QUEUE_NAME=media-images

Run a worker for the selected connection and queue:

php artisan queue:work redis --queue=media-images --tries=3

When using the application's default connection and queue, run php artisan queue:work --tries=3. Refresh cached configuration after changing these settings if your application uses config:cache.

With a queued connection, the HTTP request stores the source and a media record, then dispatches ProcessImage after the database transaction commits. The worker resizes the image, converts it to WebP using the existing image component, and creates the thumbnail when enabled. Uploaded files and local paths are copied without decoding; already decoded in-memory images are staged as PNG to preserve their contents.

The returned array keeps its existing keys. Image paths refer to the output that the worker will create, so they become available when processing finishes; the returned thumbnail is initially null. The media record has is_active = 0 until processing succeeds, then is_active = 1 and the appropriate has_thumb value. Public and private storage follow the existing visibility settings.

The source is stored under media.privatePath/processing, outside public media directories, and retained between retry attempts. When Laravel marks the job as finally failed (for example, after exhausting --tries=3), its failed() callback saves the original image bytes without resizing, conversion, or thumbnail generation. It updates the same media record to the source's actual format, sets is_active = 1 and has_thumb = 0, and removes any incomplete processed output. The staged source is retained to allow conversion to be retried later. Read the updated media->url to get the original image's URL, since its extension may differ from the initially returned WebP path. If the source is corrupt or even copying fails, the record remains inactive, the source is retained, and the fallback error is reported. HTTP and queue workers must share access to the configured media paths.

After fixing the conversion error, retry failed jobs using Laravel's existing commands:

php artisan queue:failed
php artisan queue:retry --queue=media-images

Replace media-images with your configured queue name. To retry just one job, use php artisan queue:retry <job-id>. Failed-job storage must be enabled in the project's queue configuration, and the worker for the selected connection and queue must be running. When deploying a code fix to long-running workers, restart them using the project's normal deployment process (php artisan queue:restart for standard workers).

Retried jobs use their original dimensions, quality, visibility, target format, connection, and queue. A retry converts the retained source and updates the same media record; the published original remains available until conversion succeeds. After success, the job removes the staged source and the previous original file when its path differs from the converted output. Repeated failure preserves the original and source for another retry. Older JPEG/PNG fallback jobs whose staged source was already deleted can recover it from the published original. If you permanently discard failed jobs instead of retrying them, remove their unused staged source files too. Retries are explicit; fixing code does not automatically requeue failed jobs.

QUEUE_CONNECTION=sync executes inside the current request and provides no background processing. The library preserves immediate processing for connections whose driver is sync (including custom connection names) or null, even when MEDIA_QUEUE_IMAGES=true. With MEDIA_QUEUE_IMAGES=false (the default), existing behavior remains unchanged regardless of the project's queue connection. See Laravel's queue documentation for connection and worker setup.

Create Temporary Image

Creates a temporary image.

$media_controller->saveTempImage($model, $model_id, $file);

Convert Temporary Image

Converts a temporary image into a normal media file and associates it with a model.

$media_controller->convertTempImage($model, $model_id, $media->id);

Delete Temporary Image

Deletes a temporary image and its media record.

$media_controller->deleteTemp();

Save Audio File

Saves an audio file and associates it with a model.

$media_controller->audio($model, $model_id, $file);

Save Video File

Saves a video file and associates it with a model.

$media_controller->video($model, $model_id, $file);

Save Document File

Saves a document or other uploaded file and associates it with a model.

$media_controller->uploadFile($model, $model_id, $file);

Private Media

Media can be stored as either:

public

or:

private

The default visibility is:

public

Existing package usage therefore remains backward compatible.

Private files are stored outside the publicly accessible storage directory and are served through an authenticated signed route.

To save private media, pass:

'private'

as the visibility parameter.

Save Private Image

$media_controller->saveImage($model, $model_id, $file, $format, $maxWidth, $maxHeight, $quality, 'private');

Example:

$media_controller->saveImage('order', $order->id, $file, null, 1600, 1600, 75, 'private');

Save Private Document

$media_controller->uploadFile($model, $model_id, $file, 'private');

Example:

$media_controller->uploadFile('order_receipt_invoice', $order->id, $file, 'private');

Save Private Audio

$media_controller->audio($model, $model_id, $file, 'private');

Save Private Video

$media_controller->video($model, $model_id, $file, 'private');

Private Media Authentication Guards

Private media authentication is controlled by the configured authentication guards.

By default, the package uses the sanctum guard:

'privateAuthGuards' => env('MEDIA_PRIVATE_AUTH_GUARDS', 'sanctum'),

If the application already uses Sanctum, no additional configuration is required.

Single Guard

For a JWT-based application using the api guard:

MEDIA_PRIVATE_AUTH_GUARDS=api

For another custom guard:

MEDIA_PRIVATE_AUTH_GUARDS=delegate

Multiple Guards

The package also supports multiple authentication guards.

Configure them as a comma-separated list:

MEDIA_PRIVATE_AUTH_GUARDS=sanctum,api,delegate

The package checks the configured guards in order and uses the first guard that has an authenticated user.

Conceptually:

$guards = explode(
    ',',
    config('media.privateAuthGuards', 'sanctum')
);

foreach ($guards as $guard) {
    $user = Auth::guard(trim($guard))->user();

    if ($user) {
        return $user;
    }
}

This allows the same media library to work with applications that have multiple authentication systems, such as:

sanctum
api
admin
delegate

The frontend does not need to know which guard is being used.

The frontend only needs to send the authentication credentials required by the application, such as a Bearer token or the configured authentication mechanism.

The package handles guard resolution internally.

Private Media URL

Private media URLs are generated automatically from the url attribute.

$media->url;

Example response:

https://example.com/api/media/private/10?auth_id=1&signature=...

The package automatically generates:

  • Media ID
  • Authenticated user ID
  • Signed URL signature

The application should not manually create or modify auth_id or signature.

For example:

'image' => $media->url,

may return:

{
    "image": "https://example.com/api/media/private/10?auth_id=1&signature=..."
}

The signed URL is bound to the authenticated user for whom it was generated.

When the private media endpoint is requested, the package verifies:

  1. The signed URL is valid.
  2. The media record is private.
  3. The request contains a valid authenticated user.
  4. The authenticated user's ID matches the signed auth_id.
  5. The physical private file exists.

If any verification fails, the endpoint returns:

404 Not Found

Access Private Media From Frontend

Bearer Token Authentication

When using JWT, Sanctum API tokens, or another Bearer Token authentication system, private media must be requested with:

Authorization: Bearer YOUR_TOKEN

Do not directly use the private URL like this with JWT:

<img src="PRIVATE_MEDIA_URL">

A normal <img> request does not automatically attach a Bearer Token.

Instead, request the private URL through an authenticated HTTP request.

JavaScript Fetch

const response = await fetch(media.url, { headers: { Authorization: `Bearer ${token}` } });

Convert the response to a Blob:

const blob = await response.blob();

Create a browser URL:

const imageUrl = URL.createObjectURL(blob);

Display it:

<img src="BLOB_URL">

Complete example:

const response = await fetch(media.url, { headers: { Authorization: `Bearer ${token}` } });
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);

Vue

const response = await fetch(media.url, { headers: { Authorization: `Bearer ${token}` } });
const blob = await response.blob();
imageUrl.value = URL.createObjectURL(blob);

Then:

<img :src="imageUrl">

React

const response = await fetch(media.url, { headers: { Authorization: `Bearer ${token}` } });
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);

Then:

<img src={imageUrl} alt="" />

Axios

const response = await axios.get(media.url, { headers: { Authorization: `Bearer ${token}` }, responseType: 'blob' });

Then:

const imageUrl = URL.createObjectURL(response.data);

Authenticated API Client

If the application already has an HTTP client that automatically sends the authentication token, the token does not need to be manually added again.

Example:

const blob = await $api(media.url, { responseType: "blob" });

Then:

const imageUrl = URL.createObjectURL(blob);

The important requirement is that the request reaching the private media endpoint contains the application's authentication credentials.

For JWT or Bearer Token authentication, the request must contain:

Authorization: Bearer YOUR_TOKEN

Private Thumbnail

Private thumbnails are also returned as protected signed URLs.

Example:

$media->thumb_url;

A private thumbnail may return:

https://example.com/api/media/private/10?auth_id=1&thumb=1&signature=...

It should be requested using the same authenticated process as the original private media file.

Example:

const response = await fetch(media.thumb_url, { headers: { Authorization: `Bearer ${token}` } });
const blob = await response.blob();
const thumbnailUrl = URL.createObjectURL(blob);

Public vs Private Media

Public Media

Public media can be displayed directly.

<img :src="media.url">

Example URL:

https://example.com/storage/media/images/order/10-image.webp

No authenticated media request is required.

Private Media

Private media is accessed through a protected API endpoint.

media.url
    ↓
Authenticated HTTP Request
    ↓
Authorization: Bearer TOKEN
    ↓
Signed URL Validation
    ↓
User Authentication
    ↓
User Identity Validation
    ↓
Private File
    ↓
Blob
    ↓
<img>

In short:

Public  -> Use media.url directly
Private -> Request media.url with authentication

Media Object

Explanation of the media object properties:

  • id: The unique identifier of the media object.
  • model: The model associated with the media object.
  • format: The file format.
  • model_id: The ID of the associated model.
  • order: The order of the media object when a model has multiple media files.
  • file_name: The unique file name.
  • has_thumb: Indicates whether a thumbnail exists.
  • is_active: Indicates whether the media is active.
  • is_temp: Indicates whether the media is temporary.
  • visibility: Defines whether the media is public or private.
  • deleted_at: The media deletion timestamp.
  • created_at: The media creation timestamp.
  • updated_at: The media last update timestamp.
  • url: Public URL for public media or signed authenticated URL for private media.
  • thumb_url: Public thumbnail URL or protected signed thumbnail URL when available.

Example:

{
    "id": 10,
    "model": "order",
    "model_id": 500,
    "format": "webp",
    "visibility": "private",
    "url": "https://example.com/api/media/private/10?auth_id=1&signature=...",
    "thumb_url": "https://example.com/api/media/private/10?auth_id=1&thumb=1&signature=..."
}

Rules

Square Image

Use SquareImageRule to validate that an uploaded image is square.

use DoniaShaker\MediaLibrary\Rules\SquareImageRule;

Usage:

'file' => [..., new SquareImageRule],

Troubleshooting and Collaboration

If you encounter any issues or have suggestions, please feel free to open an issue on GitHub.