hozien/laravel-awesome-uploader
Production-ready, pluggable file uploader for Laravel with Blade/React/Vue components. Supports any file type, disk storage (local/S3), JSON API responses, optional DB records + soft deletes, policies/guest uploads, hash deduplication, thumbnails, image processing, and cleanup tools.
A production-ready, customizable and pluggable file uploader for Laravel that supports Blade, React, and Vue with enterprise-level features.
intervention/image).path, url, type, name, size, file_hash, and (if enabled) id.





Install the package via composer:
composer require hozien/laravel-awesome-uploader
Publish the package's assets:
php artisan vendor:publish --provider="Hozien\Uploader\UploaderServiceProvider"
This publishes:
config/uploader.phpresources/views/vendor/uploaderpublic/vendor/uploaderdatabase/migrationsresources/lang/vendor/uploaderFor Image Processing Features:
composer require intervention/image
Version Compatibility:
Why this is needed:
What happens without it:
Required for database integration:
php artisan vendor:publish --tag=uploader-migrations
php artisan migrate
This creates the uploads table with:
Add these to your .env file for easy configuration:
# Storage
UPLOADER_DISK=public
UPLOADER_SAVE_TO_DB=true
# File Validation
UPLOADER_MAX_SIZE=2048
UPLOADER_STRICT_MIME=true
# File Deduplication
UPLOADER_CHECK_DUPLICATES=true
UPLOADER_RETURN_EXISTING=true
# Image Processing
UPLOADER_IMAGE_OPTIMIZATION=true
UPLOADER_IMAGE_QUALITY=85
UPLOADER_AUTO_ORIENT=true
# Thumbnail Generation
UPLOADER_GENERATE_THUMBNAILS=true
UPLOADER_THUMBNAIL_QUALITY=80
# Guest Uploads
UPLOADER_ALLOW_GUESTS=true
UPLOADER_GUEST_LIMIT=10
# Performance
UPLOADER_PAGINATION_LIMIT=20
# Security & Cleanup
UPLOADER_SOFT_DELETES=true
UPLOADER_AUTO_CLEANUP=false
UPLOADER_CLEANUP_DAYS=30
# Logging
UPLOADER_ENABLE_LOGGING=false
UPLOADER_LOG_CHANNEL=daily
Customize behavior in config/uploader.php:
return [
// Storage configuration
'disk' => env('UPLOADER_DISK', 'public'),
'save_to_db' => env('UPLOADER_SAVE_TO_DB', false),
// File validation
'allowed_file_types' => [
'jpg', 'jpeg', 'png', 'gif', 'webp',
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
'txt', 'csv', 'zip', 'rar'
],
'max_size' => env('UPLOADER_MAX_SIZE', 2048), // KB
'strict_mime_validation' => env('UPLOADER_STRICT_MIME', true),
// File deduplication
'check_duplicates' => env('UPLOADER_CHECK_DUPLICATES', true),
'return_existing_on_duplicate' => env('UPLOADER_RETURN_EXISTING', true),
// Image processing (requires intervention/image)
'image_optimization' => env('UPLOADER_IMAGE_OPTIMIZATION', true),
'image_quality' => env('UPLOADER_IMAGE_QUALITY', 85),
'auto_orient' => env('UPLOADER_AUTO_ORIENT', true),
// Thumbnail generation
'generate_thumbnails' => env('UPLOADER_GENERATE_THUMBNAILS', true),
'thumbnail_sizes' => [150, 300, 600], // pixels
'thumbnail_quality' => env('UPLOADER_THUMBNAIL_QUALITY', 80),
// Guest uploads
'allow_guests' => env('UPLOADER_ALLOW_GUESTS', true),
'guest_upload_limit' => env('UPLOADER_GUEST_LIMIT', 10),
'guest_token_resolver' => function () {
return session()->getId();
},
// Smart user/admin logic
'user_resolver' => function () { return auth()->user(); },
'admin_resolver' => function ($user) {
return $user && property_exists($user, 'is_admin') && $user->is_admin;
},
'uploads_query' => function ($query, $user, $isAdmin) {
if ($isAdmin) return $query;
return $query->where('user_id', $user ? $user->id : null);
},
];
โ ๏ธ Important: Image processing features require the intervention/image package. Install it first:
composer require intervention/image
# Enable/disable image optimization (default: true)
UPLOADER_IMAGE_OPTIMIZATION=true
# Image quality for optimization (1-100, default: 85)
UPLOADER_IMAGE_QUALITY=85
# Auto-correct image orientation from EXIF data (default: true)
UPLOADER_AUTO_ORIENT=true
What these do:
UPLOADER_IMAGE_OPTIMIZATION: Enables compression and optimization of uploaded imagesUPLOADER_IMAGE_QUALITY: Controls compression level (higher = better quality, larger file)UPLOADER_AUTO_ORIENT: Fixes images that appear rotated due to EXIF orientation data# Enable/disable automatic thumbnail generation (default: true)
UPLOADER_GENERATE_THUMBNAILS=true
# Quality for thumbnail images (1-100, default: 80)
UPLOADER_THUMBNAIL_QUALITY=80
Thumbnail Sizes (configured in config/uploader.php):
'thumbnail_sizes' => [150, 300, 600], // Width in pixels
Generated Files:
uploads/image.jpguploads/image_thumb_150.jpg (150px wide)uploads/image_thumb_300.jpg (300px wide)uploads/image_thumb_600.jpg (600px wide)1. Verify Installation:
# Check if intervention/image is installed
composer show intervention/image
2. Test Upload Response: Upload an image and check the response for:
{
"success": true,
"path": "uploads/image.jpg",
"thumbnails": {
"150": {
"path": "uploads/image_thumb_150.jpg",
"url": "http://your-app.com/storage/uploads/image_thumb_150.jpg",
"size": 12345
},
"300": { ... },
"600": { ... }
}
}
3. Check File Sizes:
Problem: No thumbnails generated
intervention/image packageProblem: Images still large after optimization
UPLOADER_IMAGE_QUALITY value (try 70-80)Problem: Thumbnails not appearing
Problem: Images appear rotated
UPLOADER_AUTO_ORIENT=trueRecommended Settings for Production:
UPLOADER_IMAGE_QUALITY=80 # Good balance of quality/size
UPLOADER_THUMBNAIL_QUALITY=75 # Thumbnails can be lower quality
UPLOADER_GENERATE_THUMBNAILS=true # Enable for responsive design
The uploader provides comprehensive logging for both backend and frontend operations. You can control logging behavior through environment variables:
Backend logging is controlled by Laravel's logging system and the uploader's configuration:
# Enable/disable uploader-specific logging (default: false)
UPLOADER_ENABLE_LOGGING=false
# Laravel log channel for uploader logs (default: daily)
UPLOADER_LOG_CHANNEL=daily
Backend Logging Features:
Frontend console logging is also controlled by the UPLOADER_ENABLE_LOGGING setting:
When UPLOADER_ENABLE_LOGGING=true:
When UPLOADER_ENABLE_LOGGING=false (default):
# Development environment - enable detailed logging
UPLOADER_ENABLE_LOGGING=true
UPLOADER_LOG_CHANNEL=daily
# Production environment - disable logging for performance
UPLOADER_ENABLE_LOGGING=false
UPLOADER_LOG_CHANNEL=daily
Example Log Output:
[2025-07-26 22:42:20] uploader.INFO: File uploaded successfully {"file":"example.jpg","size":26020,"user_id":null,"guest_token":"guest-abc123"}
[2025-07-26 22:42:21] uploader.INFO: Thumbnails generated for example.jpg {"thumbnails":["150x150","300x300","600x600"]}
[2025-07-26 22:42:22] uploader.WARNING: Duplicate file detected {"existing_file":"example.jpg","new_file":"example.jpg","hash":"abc123"}
Basic Usage:
<!-- Add a button to open the uploader -->
<button onclick="window.dispatchEvent(new Event('open-uploader'))">
Open Uploader
</button>
<!-- Include the uploader component -->
<x-uploader::popup
:saveToDb="true"
:multiple="true"
/>
**Component Options:**
- `:saveToDb="true"` โ Save uploads to database (default: config value)
- `:multiple="true"` โ Allow multiple file uploads (default: true)
- All labels and options are customizable via props or translation files
### React Component
```jsx
import React from "react";
import Uploader from "../vendor/uploader/react/Uploader";
function MyComponent() {
const handleUploadSuccess = (response) => {
console.log("Upload successful:", response);
// Check for duplicates
if (response.is_duplicate) {
alert("File already exists, using existing copy");
}
// Handle thumbnails
if (response.thumbnails) {
console.log("Generated thumbnails:", response.thumbnails);
}
};
const handleUploadError = (error) => {
console.error("Upload failed:", error);
// Handle specific errors
if (error.errors) {
error.errors.forEach((err) => console.error(err));
}
};
return (
<div>
<h1>My React App</h1>
<Uploader
onUploadSuccess={handleUploadSuccess}
onUploadError={handleUploadError}
multiple={true}
saveToDb={true}
/>
</div>
);
}
export default MyComponent;
<template>
<div>
<h1>My Vue App</h1>
<Uploader
@upload-success="onUploadSuccess"
@upload-error="onUploadError"
:multiple="true"
:save-to-db="true"
/>
</div>
</template>
<script>
import Uploader from "../vendor/uploader/vue/Uploader.vue";
export default {
components: {
Uploader,
},
methods: {
onUploadSuccess(response) {
console.log("Upload successful:", response);
// Handle duplicates
if (response.is_duplicate) {
this.$toast.info("File already exists, using existing copy");
}
// Handle thumbnails
if (response.thumbnails) {
this.handleThumbnails(response.thumbnails);
}
},
onUploadError(error) {
console.error("Upload failed:", error);
this.$toast.error("Upload failed: " + error.message);
},
handleThumbnails(thumbnails) {
// Process generated thumbnails
Object.entries(thumbnails).forEach(([size, thumbnail]) => {
console.log(`${size}px thumbnail:`, thumbnail.url);
});
},
},
};
</script>
The package provides several API endpoints for file management:
Single File Upload:
POST /api/uploader/upload
Content-Type: multipart/form-data
file: [file]
saveToDb: true|false
guest_token: [optional for guests]
Multiple File Upload:
POST /api/uploader/upload
Content-Type: multipart/form-data
files[]: [file1, file2, ...]
multiple: true
saveToDb: true|false
guest_token: [optional for guests]
Response Format:
{
"success": true,
"path": "uploads/filename.jpg",
"url": "/storage/uploads/filename.jpg",
"type": "image/jpeg",
"name": "original-filename.jpg",
"size": 12345,
"file_hash": "md5hash",
"is_duplicate": false,
"id": 123,
"thumbnails": {
"150": {
"path": "uploads/filename_thumb_150.jpg",
"url": "/storage/uploads/filename_thumb_150.jpg",
"size": 3456
}
}
}
GET /api/uploader/uploads?page=1&per_page=20&type=images&search=filename
Query Parameters:
page - Page number for paginationper_page - Items per page (max 100)type - Filter by type: images, documents, or specific MIME typesearch - Search by filenamesort_by - Sort field (default: created_at)sort_order - Sort direction: asc or descguest_token - Required for guest uploadsResponse:
{
"data": [
{
"id": 123,
"name": "filename.jpg",
"path": "uploads/filename.jpg",
"url": "/storage/uploads/filename.jpg",
"type": "image/jpeg",
"size": 12345,
"file_hash": "md5hash",
"formatted_size": "12.1 KB",
"is_image": true,
"user_id": 1,
"guest_token": null,
"created_at": "2024-01-01T00:00:00.000000Z",
"permissions": {
"view": true,
"delete": true,
"download": true
}
}
],
"current_page": 1,
"per_page": 20,
"total": 100
}
DELETE /api/uploader/uploads/{id}
GET /api/uploader/stats
Response:
{
"total_files": 50,
"total_size": 1048576,
"total_size_formatted": "1 MB",
"image_count": 30,
"document_count": 20
}
POST /api/uploader/cleanup
Response:
{
"cleaned": 5,
"files": ["uploads/orphaned1.jpg", "uploads/orphaned2.pdf"]
}
The package automatically detects duplicate files using MD5 hashing:
// In your controller or service
$uploader = app('uploader');
// Check for duplicates before upload
$duplicate = $uploader->findDuplicate($uploadedFile);
if ($duplicate) {
// Handle duplicate - return existing file or allow new upload
}
Automatic thumbnail generation for images:
// Generate thumbnails manually
$imageProcessor = new ImageProcessor();
$thumbnails = $imageProcessor->generateThumbnails($imagePath, [
'sizes' => [150, 300, 600],
'thumbnail_quality' => 80
]);
// Delete thumbnails when removing files
$imageProcessor->deleteThumbnails($imagePath);
Get comprehensive upload statistics:
$uploader = app('uploader');
// Get stats for current user
$userStats = $uploader->getUploadStats(auth()->id());
// Get stats for all users (admin)
$allStats = $uploader->getUploadStats();
Clean up orphaned files:
$uploader = app('uploader');
$result = $uploader->cleanupOrphanedFiles();
echo "Cleaned {$result['cleaned']} orphaned files";
Multi-layer validation includes:
Comprehensive policy-based permissions:
// Customize permissions in UploadPolicy
public function delete(User $user, Upload $upload)
{
// Custom logic for delete permissions
return $user->hasRole('admin') ||
($user->id === $upload->user_id && $user->can('delete-own-uploads'));
}
The package includes comprehensive tests:
# Run all tests
php artisan test
# Run specific test suites
php artisan test tests/Feature/UploaderTest.php
php artisan test tests/Unit/ImageProcessorTest.php
Test Coverage:
Allow non-authenticated users to upload files:
// Configuration
'allow_guests' => true,
'guest_upload_limit' => 10,
'guest_token_resolver' => function () {
return session()->getId();
},
Frontend Usage:
// Include guest token in upload requests
const guestToken = "unique-session-id";
fetch("/api/uploader/upload", {
method: "POST",
body: formData.append("guest_token", guestToken),
});
Enhanced event system for frontend integration:
| Event Name | Description | Payload |
|---|---|---|
files-selected |
Files selected for upload | { files: FileList } |
upload-start |
Upload process begins | { files: FileList } |
upload-success |
Upload completed successfully | { response: Object } |
upload-error |
Upload failed | { error: Object } |
upload-progress |
Upload progress update | { progress: Number } |
duplicate-detected |
Duplicate file detected | { file: File, existing: Object } |
thumbnail-generated |
Thumbnail generation complete | { thumbnails: Object } |
The package includes several performance optimizations:
Image Processing Not Working:
intervention/image package: composer require intervention/imageUpload Fails:
Thumbnails Not Generated:
intervention/image is installedEnable detailed logging for debugging:
UPLOADER_ENABLE_LOGGING=true
UPLOADER_LOG_CHANNEL=daily
View logs:
tail -f storage/logs/laravel.log
/api/ to /api/uploader/file_hash, is_duplicate, etc.)file_hash column requiredThe MIT License (MIT). Please see License File for more information.
Please see CONTRIBUTING.md for details.
Laravel Awesome Uploader - Production-ready file uploading with enterprise-level features.
How can I help you explore Laravel packages today?