ghostcompiler / uploads-manager
Manage your uploads easily
Requires
- php: ^8.1
- illuminate/support: ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0
Suggests
- ext-gd: Required for image compression and AVIF conversion support.
- laravel/framework: Required in the host application to provide Eloquent, routing, console, HTTP, and filesystem integration.
README
A Laravel package for upload storage, secure file URLs, model-based URL fields, inline preview support, file removal, and optional image optimization to AVIF.
Overview
Uploads Manager is built to keep file handling simple inside Laravel apps.
It gives you:
- one upload method for
publicandprivatefiles - automatic storage using Laravel
Storage - fixed upload folders inside
storage/app/UploadsManager - database tracking for uploaded files
- database tracking for generated file links
- model integration through one trait
- hidden raw upload IDs with clean URL fields like
avatar - browser preview for supported file types
- forced download support with
?download=1 - optional image optimization with AVIF conversion
Folder structure
By default, files are stored here:
storage/app/UploadsManager/public
storage/app/UploadsManager/private
Database tables
The package manages two tables:
uploads_manager_uploads
Stores:
- storage disk
- visibility
- file path
- original file name
- mime type
- extension
- size
- metadata
uploads_manager_links
Stores:
- upload reference
- generated token
- expiry time
- last accessed time
This allows the package to create and manage URLs in a clean way.
Installation Guide
Step 1: Install the package
composer require ghostcompiler/uploads-manager
Step 2: Publish config and migrations
php artisan uploads-manager:install
This publishes:
config/uploads-manager.php- migration for
uploads_manager_uploads - migration for
uploads_manager_links
Step 3: Run migrations
php artisan migrate
Step 4: Add upload ID columns to your own tables
The package stores file metadata in its own tables, but your own models still need a field like avatar_id, resume_id, or document_id.
Example:
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::table('users', function (Blueprint $table) { $table->unsignedBigInteger('avatar_id')->nullable(); }); } public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn('avatar_id'); }); } };
Step 5: Add the trait to your model
Import this trait:
use GhostCompiler\UploadsManager\Concerns\UploadsManager;
Then use it in your model:
use GhostCompiler\UploadsManager\Concerns\UploadsManager; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use UploadsManager; }
This trait is required for automatic URL field handling.
Step 6: Define uploadable fields
Now define protected $uploadable on the model.
Example:
use GhostCompiler\UploadsManager\Concerns\UploadsManager; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use UploadsManager; protected $uploadable = [ 'avatar_id' => [ 'name' => 'avatar', 'type' => 'public', 'id' => 'hide', 'expiry' => 100, ], 'resume_id' => [ 'name' => 'resume', 'type' => 'private', 'id' => 'hide', 'expiry' => 30, ], ]; }
Quick Usage
Upload a file
use GhostCompiler\UploadsManager\Facades\Uploads; $upload = Uploads::upload('public', $request->file('avatar')); $user->avatar_id = $upload->id; $user->save();
Read file URL from the model
$user->avatar;
If avatar_id is mapped with:
'avatar_id' => [ 'name' => 'avatar', ]
then:
avatar_idstays in the databaseavatarbecomes the returned URL field
Remove a file
Uploads::remove($user->avatar_id);
Full Example
Example model
<?php namespace App\Models; use GhostCompiler\UploadsManager\Concerns\UploadsManager; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; #[Fillable(['name', 'email', 'phone', 'password', 'role', 'avatar_id'])] #[Hidden(['password'])] class User extends Authenticatable { use Notifiable, HasUuids, HasApiTokens, UploadsManager; protected $casts = [ 'password' => 'hashed', ]; protected $uploadable = [ 'avatar_id' => [ 'name' => 'avatar', 'type' => 'public', 'id' => 'hide', 'expiry' => 60, ], ]; }
Example controller logic
use App\Models\User; use GhostCompiler\UploadsManager\Facades\Uploads; use Illuminate\Http\Request; public function updateProfile(Request $request) { $user = auth()->user(); if ($request->hasFile('avatar')) { $upload = Uploads::upload('public', $request->file('avatar')); $user->avatar_id = $upload->id; } $user->save(); return response()->json([ 'user' => $user, ]); }
Example API response shape
{
"id": "019d810d-1499-7192-9f99-4a67c5ad350b",
"name": "Ghost Compiler",
"email": "ghost@example.com",
"avatar": "https://your-app.test/_uploads-manager/file/your-token"
}
Preview and Download Behavior
Previewable files open directly in the browser. Other files download automatically.
Preview currently supports:
image/avifimage/jpegimage/pngimage/gifimage/webpimage/svg+xmlapplication/pdftext/plain
Example preview URL:
https://your-app.test/_uploads-manager/file/your-token
If you want to force download for any file:
https://your-app.test/_uploads-manager/file/your-token?download=1
Image Optimization
The package can optimize uploaded images globally.
When enabled:
- resolution stays the same
- file size is reduced
- supported images are converted to AVIF
- browser delivery becomes lighter and faster
Supported input image types:
image/jpegimage/pngimage/webp
If the server cannot encode AVIF, the package falls back to the original uploaded file.
Config Guide
Published config file:
return [ 'disk' => 'local', 'base_path' => 'UploadsManager', 'paths' => [ 'public' => 'public', 'private' => 'private', ], 'defaults' => [ 'type' => 'private', 'id' => 'hide', 'expiry' => 60, ], 'image_optimization' => [ 'enabled' => false, 'quality' => 75, 'convert_to_avif' => true, ], 'preview_mime_types' => [ 'image/avif', 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml', 'application/pdf', 'text/plain', ], 'delete_files_with_model' => false, 'route' => [ 'prefix' => '_uploads-manager', 'name' => 'uploads-manager.show', 'middleware' => ['web'], ], ];
Config key reference
disk
Laravel disk used for storing package files.
Example:
'disk' => 'local'
base_path
Base folder inside the selected Laravel disk.
Example:
'base_path' => 'UploadsManager'
paths.public
Subfolder used for public uploads.
Default result:
UploadsManager/public
paths.private
Subfolder used for private uploads.
Default result:
UploadsManager/private
defaults.type
Default upload type for model mapping when not explicitly given.
Allowed values:
publicprivate
defaults.id
Controls whether the raw upload ID field should remain visible.
Recommended:
'id' => 'hide'
defaults.expiry
Default link expiry in minutes.
Example:
'expiry' => 60
image_optimization.enabled
Enable or disable global image optimization.
Example:
'enabled' => true
image_optimization.quality
Compression quality from 1 to 100.
- lower value = smaller file size
- higher value = better visual quality
Recommended starting point:
'quality' => 75
image_optimization.convert_to_avif
If enabled, supported uploaded images are converted to AVIF.
Example:
'convert_to_avif' => true
preview_mime_types
List of mime types that should open inline in the browser instead of downloading.
delete_files_with_model
If enabled, deleting the model also deletes the stored file and related upload record.
Example:
'delete_files_with_model' => true
route.prefix
URL prefix for generated file links.
Default:
_uploads-manager
route.name
Laravel route name used internally by the package.
route.middleware
Middleware applied to generated file serving routes.
Default:
['web']
Uploadable Field Options
Each entry in protected $uploadable supports:
name
The returned attribute name.
Example:
'name' => 'avatar'
This means:
- database field:
avatar_id - returned URL field:
avatar
type
Upload visibility type.
Allowed values:
publicprivate
id
Controls whether the original DB ID field should be visible in serialized output.
Recommended:
'id' => 'hide'
expiry
Custom link expiry for this field in minutes.
Example:
'expiry' => 100
Notes
Uploads::upload()only acceptspublicorprivate- generated URLs are tracked in the database
- file preview and download behavior are handled automatically
- image optimization only applies to supported image uploads
- AVIF conversion needs GD support with AVIF enabled on the server
ext-gdis suggested for image compression support
Recommended Setup Example
protected $uploadable = [ 'avatar_id' => [ 'name' => 'avatar', 'type' => 'public', 'id' => 'hide', 'expiry' => 60, ], 'document_id' => [ 'name' => 'document', 'type' => 'private', 'id' => 'hide', 'expiry' => 15, ], ];
This gives you:
$user->avatarfor a public previewable file URL$user->documentfor a private file URL- hidden raw upload ID fields in responses
Developer Guide
If you want to improve the package or contribute features, this section is for you.
Local development flow
- Clone the repository.
- Create your feature branch.
- Make your changes.
- test the package inside a Laravel app.
- update the README if behavior changes.
- open a pull request with a clear explanation.
Example:
git clone <your-repo-url> cd UploadsManager git checkout -b feature/your-feature-name
Suggested development setup
Use this package with a local Laravel test project so you can verify:
- uploads work
- generated URLs open correctly
- preview and forced download both work
- model serialization returns the expected URL fields
- image optimization works when enabled
Before opening a pull request
Please check:
- code is clean and readable
- package behavior is documented
- no unrelated files are changed
- syntax checks pass
- new config options are documented
Contribution ideas
Some good areas to contribute:
- more file transformation options
- better test coverage
- artisan helpers for adding upload columns
- cleanup tools for expired links
- support for more image drivers
Support the project
If this package helps you, please support it:
- star the repository
- share it with other Laravel developers
- open issues with clear reproduction steps
- contribute fixes and improvements
How to report bugs
When opening an issue, include:
- Laravel version
- PHP version
- package version or branch
- database driver
- exact error message
- steps to reproduce
Pull request notes
A good pull request should include:
- what changed
- why it changed
- how it was tested
- whether README or config docs were updated
