donia-shaker / media-library
Requires
- intervention/image: ^3.6
- ivoglent/ffmpeg-composer-bin: ^1.3
- laravel/framework: >=10.0.0 <13.0.0
Requires (Dev)
- laravel/pint: ^1.15
- orchestra/testbench: ^9.0
- phpunit/phpunit: ^11.1
README
This package provides a set of functions for handling media files, including images, audio, video, PDF files, and other documents.
- Media Library
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
MEDIA_USE_STORAGE=true MEDIA_CREATE_THUMBNAILS=true
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.
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:
- The signed URL is valid.
- The media record is private.
- The request contains a valid authenticated user.
- The authenticated user's ID matches the signed
auth_id. - 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 ispublicorprivate.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.