kwaadpepper / laravel-storage-manager
A Laravel file manager that can handle multiple storages
Package info
github.com/Kwaadpepper/Laravel-Storage-Manager
pkg:composer/kwaadpepper/laravel-storage-manager
Requires
- php: ^8.4
- laravel/framework: ^12.0|^13.0
- symfony/polyfill-php86: ^1.43
- webmozart/assert: ^2.5
Requires (Dev)
- ace-of-aces/intellipest: ^0.3.0
- larastan/larastan: ^3.9
- laravel/pint: ^1.29
- league/flysystem-aws-s3-v3: ^3.32
- league/flysystem-ftp: ^3.31
- league/flysystem-sftp-v3: ^3.31
- orchestra/testbench: ^10.11
- pestphp/pest: ^4.4
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.1
- pestphp/pest-plugin-type-coverage: ^4.0
- phpstan/phpstan: ^2.1
- slevomat/coding-standard: ^8.28
- squizlabs/php_codesniffer: ^4.0
- symfony/dependency-injection: ^7.0|^8.0
Suggests
- ext-zip: Required to compress and extract zip archives
- league/flysystem-aws-s3-v3: Required to use S3 storage
- league/flysystem-ftp: Required to use FTP storage
- league/flysystem-sftp-v3: Required to use SFTP storage
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-08 16:17:10 UTC
README
A modern, embeddable, multi-storage file manager for Laravel applications with Vue 3, Tailwind CSS v4, DaisyUI 5, and full support for Local, Amazon S3, MinIO, FTP, and SFTP.
✨ Features
- Multi-Storage Driver Abstraction: Seamlessly manage files across Local, S3, MinIO, Cloudflare R2, FTP, SFTP, or custom Flysystem adapters via dedicated domain strategies.
- Chunked Resumable Uploads: Multi-part chunked upload pipeline with client-side MD5 hashing, atomic locks on upload sessions, progress tracking, and conflict policies (
auto_rename,overwrite,error). - WYSIWYG & Form Integrations: Ready-to-use plugins, helpers, and zero-dependency callbacks for:
- Standalone Button / Form Input (Simple
fmSetLinkcallback orStorageManagerPicker.attach()) - CKEditor 5 (Official toolbar plugin)
- CKEditor 4 (
filebrowserImageBrowseUrlconnector) - TinyMCE 5 & 6 (
file_picker_callbackintegration) - Tiptap (Custom extension &
openStorageManagerTiptap()helper)
- Standalone Button / Form Input (Simple
- Media Streaming & Preview Viewers:
- Audio & Video Streaming: HTTP 206 Partial Content range requests for smooth seeking and playback.
- Image Viewer & Cropper: Integrated image viewer with Cropper.js 2 (aspect ratio presets, 90° rotation, flip, zoom, save copy or overwrite).
- Code & Text Editor: In-browser code editing with Prism syntax highlighting and auto-save.
- PDF Preview: Full-screen inline PDF document viewer.
- Lazy-Loaded Thumbnails: On-the-fly cached WebP thumbnails with HTTP 304 (ETag) browser caching.
- Trash & Soft Delete: Soft-delete files and directories to trash with restoration, expiration pruning, and permanent purge.
- Storage Usage Statistics: Real-time capacity metrics (partition stats, configured per-disk quotas, or unlimited cloud mode) with visual storage bars and cache invalidation.
- Archive Support: Create ZIP archives and safely extract existing archives in place with zip-slip protection.
- Batch Operations: Bulk rename (with patterns like
image_{n}), copy, move, and recursive delete with confirmation. - Domain Interceptable Events: Cancellable
FileCreating,FileDeleting,FileMoving, etc. events enabling custom validation or business rules before operations execute. - Security & CSP: Automatic CSP nonce propagation in embedded layouts, filename sanitization, and strict MIME type enforcement.
📋 Requirements
- PHP:
8.4+ - Laravel:
12.0+or13.0+ - Composer:
2.x+ - Bun (for frontend development only):
1.3+
🚀 Installation
1. Install via Composer
composer require kwaadpepper/laravel-storage-manager
2. Publish Assets & Configuration
Publish the pre-compiled frontend assets and default configuration file:
# Publish built assets (resources/js, resources/css) to public/vendor/storage-manager php artisan vendor:publish --tag=storage-manager:assets # Publish config file to config/storage-manager.php php artisan vendor:publish --tag=storage-manager:config
Optional tags available:
# Publish translations to lang/vendor/storage-manager php artisan vendor:publish --tag=storage-manager:translations # Publish example blade templates for WYSIWYG editors php artisan vendor:publish --tag=storage-manager:examples
3. Open Storage Manager
Navigate to /storage-manager in your browser. (The route prefix can be customized in config/storage-manager.php).
🎨 WYSIWYG & Standalone Integrations
Laravel Storage Manager supports two approaches for integration:
- Zero-dependency callback (
fmSetLink): Simple, fast, no JavaScript dependencies needed. - Dedicated integration bundles: Included in
public/vendor/storage-manager/js/for rich modals and editors.
Ready-to-use example templates are located in examples/wysiwyg/ (publishable via php artisan vendor:publish --tag=storage-manager:examples).
1. Standalone Button / Form Input
Method A: Zero-Dependency Callback (fmSetLink)
No extra JS library needed. The popup calls window.opener.fmSetLink(url, file) automatically upon selection:
<div class="input-group"> <input type="text" id="image_url" class="form-control" name="thumbnail"> <button type="button" class="btn btn-primary" onclick="openPicker()">Choose Image</button> </div> <script> function openPicker() { window.open('/storage-manager?picker=1&type=image', 'StorageManager', 'width=1200,height=700'); } // Automatically invoked by Storage Manager function fmSetLink(url, file) { document.getElementById('image_url').value = url; } </script>
Method B: Standalone Picker Helper (StorageManagerPicker.attach)
Using the published helper bundle (storage-manager-picker.js), you can open as a popup or an embedded iframe modal:
<input type="text" id="avatar_input"> <button id="btn_avatar" type="button">Select Avatar</button> <img id="avatar_preview" src="" alt="Preview"> <script src="{{ asset('vendor/storage-manager/js/storage-manager-picker.js') }}"></script> <script> StorageManagerPicker.attach('#btn_avatar', '#avatar_input', { url: '/storage-manager', type: 'image', mode: 'modal', // 'modal' (iframe) or 'popup' preview: '#avatar_preview' }); </script>
2. CKEditor 5 Integration
Include the published integration bundle in your blade view:
<textarea id="editor" name="content"></textarea> <script src="https://cdn.ckeditor.com/ckeditor5/41.2.0/classic/ckeditor.js"></script> <script src="{{ asset('vendor/storage-manager/js/storage-manager-picker.js') }}"></script> <script src="{{ asset('vendor/storage-manager/js/storage-manager-ckeditor5.js') }}"></script> <script> ClassicEditor.create(document.querySelector('#editor'), { extraPlugins: [window.StorageManagerCKEditor5], toolbar: { items: [ 'heading', '|', 'bold', 'italic', 'link', '|', 'storageManager', '|', // Storage Manager toolbar button 'undo', 'redo' ] }, storageManager: { url: '/storage-manager', type: 'image', // 'image', 'video', 'pdf', 'archive' or omit for all mode: 'modal' // 'modal' or 'popup' } }); </script>
3. CKEditor 4 Integration
Point CKEditor's file browser directly to the Storage Manager route:
CKEDITOR.replace('editor', { filebrowserImageBrowseUrl: '/storage-manager?type=image', filebrowserBrowseUrl: '/storage-manager' });
Storage Manager captures CKEditorFuncNum and sends the selected URL straight to CKEDITOR.tools.callFunction().
4. TinyMCE 5 & 6 Integration
Configure TinyMCE's file_picker_callback:
tinymce.init({ selector: 'textarea#editor', plugins: 'image link media code', toolbar: 'undo redo | bold italic | link image media | code', file_picker_callback: function (callback, value, meta) { const type = meta.filetype === 'image' ? 'image' : (meta.filetype === 'media' ? 'video' : ''); const url = '/storage-manager?picker=1' + (type ? '&type=' + type : ''); window.open(url, 'StorageManager', 'width=1200,height=700,resizable=yes'); window.addEventListener('message', function onMessage(event) { if (event.data && event.data.file) { callback(event.data.file.url, { alt: event.data.file.name, text: event.data.file.name }); window.removeEventListener('message', onMessage); } }); } });
5. Tiptap Integration
Use the helper openStorageManagerTiptap() from storage-manager-tiptap.js:
<button type="button" id="btn-insert-image">Insert Image</button> <div id="tiptap-editor"></div> <script src="{{ asset('vendor/storage-manager/js/storage-manager-picker.js') }}"></script> <script src="{{ asset('vendor/storage-manager/js/storage-manager-tiptap.js') }}"></script> <script type="module"> import { Editor } from 'https://esm.sh/@tiptap/core'; import StarterKit from 'https://esm.sh/@tiptap/starter-kit'; import Image from 'https://esm.sh/@tiptap/extension-image'; const editor = new Editor({ element: document.querySelector('#tiptap-editor'), extensions: [StarterKit, Image] }); document.getElementById('btn-insert-image').addEventListener('click', () => { window.openStorageManagerTiptap(editor, { url: '/storage-manager', type: 'image', mode: 'modal' }); }); </script>
⚙️ Configuration Overview
In config/storage-manager.php, you can customize:
return [ 'auth' => [ 'enabled' => true, 'guard' => 'web', 'allow_by_default' => false, ], 'route' => [ 'prefix' => 'storage-manager', 'middleware' => ['web', 'auth'], ], 'disks' => [ // Available disks in the file manager 'available' => ['public', 's3', 'ftp'], 'default' => 'public', // Per-disk fine-grained configuration 'configs' => [ 'public' => [ 'root' => '', 'allowed_extensions' => ['jpg', 'png', 'pdf', 'zip'], 'trash' => true, 'quota' => 1073741824, // 1 GB 'max_file_size' => 52428800, // 50 MB 'filename_policy' => 'strict', ], 's3' => [ 'root' => 'uploads', 'trash' => false, ], ], ], 'upload' => [ 'max_file_size' => 104857600, // 100 MB fallback 'chunk_size' => 5242880, // 5 MB chunks 'duplicate_policy' => 'auto_rename', // 'auto_rename', 'overwrite', 'error' 'concurrency_lock' => [ 'driver' => 'auto', // 'cache', 'flock', or 'auto' ], ], 'trash' => [ 'enabled' => true, 'folder' => '.trash', 'retention_days' => 30, ], ];
Authorization hardening: with
allow_by_default=false(default), every Storage Manager ability must be explicitly authorized by your app (viaGate::define(...)/ policies). Setallow_by_default=trueonly if you intentionally accept permissive fallback authorization.
☁️ Cloud Storage & S3 IAM Permissions
When using Amazon S3, MinIO, or Cloudflare R2, configure your IAM policy as follows:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::your-bucket-name"
]
},
{
"Effect": "Allow",
"Action": [
"s3:DeleteObject",
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::your-bucket-name/*"
]
}
]
}
MinIO Compatibility Note: MinIO does not use S3 ACLs (
s3:GetObjectAcl,s3:PutObjectAcl). Laravel Storage Manager automatically detects compatibility and falls back to stream-based operations, ensuring full compatibility without requiring ACL policies.
🛠️ Local Development & HMR Workflow
To develop the package with true Hot Module Replacement (HMR) inside your Laravel host application:
- Configure your host
.env:STORAGE_MANAGER_USE_DEV_SERVER=true STORAGE_MANAGER_DEV_SERVER_URL=http://localhost:5173
- Start the Vite dev server in the package repository:
bun run dev
- Start your Laravel host app (
php artisan serve).
🚦 Quality Gates
This package strictly enforces 100% type coverage, PHPStan Level 10, and PSR-12 coding standards:
# 1. PHP Code Style (0 errors, 0 warnings) vendor/bin/phpcs # 2. PHP Static Analysis (Level 10 strict) composer analyse # 3. Pest Test Suite (100% pass) composer test # 4. PHP Type Coverage (100.0% enforced) composer test:type-coverage # 5. PHP Code Formatting (Pint) composer format # 6. Frontend Linting & Build bun run lint bun test bun run build
📄 License
The MIT License (MIT). Please see License File for more information.