xakki / laravel-file-uploader
Chunked file uploader package for Laravel 10+.
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Language:JavaScript
pkg:composer/xakki/laravel-file-uploader
Requires
- php: ^8.3
- illuminate/filesystem: ^10.0|^11.0
- illuminate/http: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
- illuminate/validation: ^10.0|^11.0
Requires (Dev)
- larastan/larastan: ^2.9
- laravel/pint: ^1.14
- orchestra/testbench: ^8.17
- phpstan/phpstan: ^1.11
- phpunit/phpunit: ^10.5
This package is auto-updated.
Last update: 2025-10-10 10:48:37 UTC
README
Fast, secure and convenient downloader of any files for Laravel 10+ (PHP 8.3+ ) with a modern JS widget.
Supports chunked upload (the chunk size is configured in the config, 1 MB by default), Drag&Drop, list of uploaded files (size/date), copying a public link, soft deletion to trash with TTL auto-cleanup, localization en/ru, flexible configuration and work with any disks (including s3/cloudfront).
Features
โ ๐ Chunks: sending a file in parts, the chunk size is configurable (chunk_size
), default is 1 MB.
- ๐i18n: en (default), ru.
-*Service Provider: an autodiscaver, publishing assets/config/locales.
โ ๐ฆ Any disks: default is
files
; there are ready-made recipes for s3/CloudFront (public/private). - ๐จPop-up widget: for uploading files.
- ๐ฑ๏ธDrag & Drop + file selection.
- ๐File list: name, size, date, copy public link in one click, delete.
- ๐งนDeletion to the trash (soft-delete) + auto-cleaning by TTL (default is 30 days).
โ ๐ Access via middleware (default is
web
+auth
) - changes in the config.
Content
- Installation
- [Configuration] (#configuration)
- Integration with S3 / CloudFront
- [Widget (JS)] (#widget-js)
- Routes and API
- Delete and Trash
- [Localization (i18n)] (#localization-i18n)
- [PHP Service] (#php service)
- [Security] (#security)
- [Performance] (#performance)
- FAQ
- Troubleshooting
- CI / QA / Coding Style
- Roadmap
- Contributing
- License
- Credits
Installation
composer require xakki/laravel-file-uploader
If the auto-finder is disabled, add the provider to config/app.php
:
'providers' => [ Xakki\LaravelFileUploader\Providers\FileUploaderServiceProvider::class, ],
Publish configs, assets, and translations.:
php artisan vendor:publish --tag=file-uploader-config php artisan vendor:publish --tag=file-uploader-assets php artisan vendor:publish --tag=file-uploader-translations
Make a public symlink (if not already created):
php artisan storage:link --relative
๐ก The default disk is
public
. Make sure that it is defined inconfig/filesystems.php
.
Configuration
The file: `config/file-uploader.php ' (redefine if necessary).
About the size of the chunk: the client takes the
chunk_size
from the/init
response, because the value change in the config is automatically picked up at the front.
Integration with S3 / CloudFront
Below are two proven scenarios.
Option A: S3 + CloudFront Public tank as CDN (simple public URLs)
.env
:
FILESYSTEM_DISK=s3 AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_DEFAULT_REGION=eu-central-1 AWS_BUCKET=my-public-bucket AWS_URL=https://dxxxxx.cloudfront.net AWS_USE_PATH_STYLE_ENDPOINT=false
config/filesystems.php
(fragment of the s3 driver):
's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), 'url' => env ('AWS_URL'), / / < -- cloudfront domain here 'visibility' => 'public', 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), ],
config/file-uploader.php
:
'disk' => 's3', 'public_url_resolver' => null, / / Storage:: url () returns the CloudFront URL `` > **Summary:** `Storage:: url (.path)` will build .l based on `aws_url' (CloudFront domain). --- ### Option B: S3 + CloudFront Private Tank with **signed links** If the bucket is private and file access requires a signature, use one of two paths: **B1. S3 pre-signed (temporary) URLs:** * Create a temporary URL in the controller/service along with 'Storage:: url()`: ```php $url = Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(10));
- Return it to the client (widget/listing).
-
-
- Plus**: simple and regular. Minus: The URL will be s3 format, not CloudFront.
-
B2. CloudFront Signed URL (recommended if you need a CDN domain):
- Specify in ' config / file-uploader.php ` public URL resolver (string callable; it is convenient to put the class in a package/project):
'public_url_resolver' => \App\Support\FileUrlResolvers\CloudFrontSignedResolver::class.'@resolve',
- Implement `CloudFrontSignedResolver' (example):
<?php namespace App\Support\FileUrlResolvers; use Aws\CloudFront\UrlSigner; class CloudFrontSignedResolver { public function __construct( private readonly string $domain = 'https://dxxxxx.cloudfront.net', private readonly string $keyPairId = 'KXXXXXXXXXXXX', private readonly string $privateKeyPath = '/path/to/cloudfront_private_key.pem', private readonly int $ttlSeconds = 600, / / 10 minutes ) {} public function resolve(string $path): string { // Normalizing the CloudFront URL $resourceUrl = rtrim($this->domain, '/').'/'.ltrim($path, '/'); // Signing the URL $signer = new UrlSigner($this->keyPairId, file_get_contents($this->privateKeyPath)); $expires = time() + $this->ttlSeconds; return $signer->getSignedUrl($resourceUrl, $expires); } }
Important: use the string callable (
Class@method') along with the closure โ this is compatible with
php artisan config: cache'.
Widgets (JS)
Initialization
Connecting a script widget:
<script src="/vendor/file-uploader/file-upload.js" defer></script>
Insert the container and initialize the widget (for example, in layouts/app.blade.php
):
<div id="file-upload-widget"></div> <script> window.FileUploadWidget?.init({ endpointBase: '/file-upload', chunkSize: 1024 * 1024, listEnabled: true, allowDelete: true, locale: 'en', // 'en' | 'ru' auth: 'csrf', // 'csrf' | 'bearer' | 'none' token: null, / / bearer token for API styles: {/* custom CSS */ toggle: { background: '#111827' }, modal: { maxWidth: '380px' }, dropzone: { borderColor: '#4f46e5' }, }, i18n: {/* custom Locale */ title: 'Uploads', drop: 'Drop files here or click to browse', completed: 'Done!', } }); </script>
Events
file-uploader:success
โ{ file }
file-uploader:deleted
โ{ id }
Routes and API
Prefix:'config ('file-uploader.route_prefix
)
, default is '/file-upload'. All routes are wrapped inmiddleware' from the config (default:
web,
auth`).
Redirecting chunks
POST /file-upload/chunks
Body - multipart/form-data
with fields:
filechunk'-binary chunk (<<config ('file-uploader.chunk_size')
).- `chunkIndex ' โ chunk number (0..N-1).
- `totalChunks' โ total chunks.
uploadId' is a unique ID (for example, 'upload-${Datenow()}-${Math.random()}
).- 'filesize', 'filename', 'mimetype' - metadata.
Response (200 JSON)
{ "status": "ok", "completed": true, "file": { "id": "upload-...", "original_name": "report.pdf", "size": 7340032, "mime": "application/pdf", "url": "https://example.com/storage/uploads/report.pdf", "created_at": "2025-10-09T10:12:33Z" }, "message": "File \"report.pdf\" uploaded successfully." }
If `completed = false', the service will continue to wait for the remaining chunks.
List of files
GET /file-upload/files
Response (200 JSON)
{ "status": "ok", "files": [ { "id": "upload-...", "original_name": "report.pdf", "size": 7340032, "mime": "application/pdf", "url": "https://example.com/storage/uploads/report.pdf", "created_at": "2025-10-09T10:12:33Z" } ] }
Delete and trash
Deletion (soft-delete)
DELETE /file-upload/files/{id}
Response (200 JSON)
{ "status": "ok", "message": "File moved to trash." }
Recovery
POST /file-upload/files/{id}/restore
Response (200 JSON)
{ "status": "ok", "message": "File restored." }
Emptying the trash (TTL)
php artisan file-uploader:cleanup
app/Console/Kernel.php
:
$schedule->command('file-uploader:cleanup')->daily();
Via HTTP:DELETE /file-upload/trash/cleanup
โ `{"status": "ok", "count": }'.
TTL is controlled by `trash_ttl_days' (default 30 ).
Localization (i18n)
- Server: locales from
supported_locales' (
en/
ru), default is
default_locale'. - Widget: by default, en; you can specify
locale: 'ru
and/or redefine the strings in 'i18n'.
PHP service
Xakki\LaravelFileUploader\Services\FileUpload
Responsible for:
- Validation of
size
/ 'mime' / 'extension' (config). - Receiving chunks to a temporary directory (
storage/app/chunks/{uploadId}
). - Assembling the final file and saving it to
Storage::disk ()...)
. - Generation of a public or signed link (via
public_url_resolver
/Storage::url
/temporaryUrl
). - Transfer/restore files from the trash.
- Clearing temporary chunks and trash (command/shadower).
Example:
use Xakki\LaravelFileUploader\Services\FileUpload; /** @var FileUpload $uploader */ $uploader = app(FileUpload::class); $result = $uploader->handleChunk([ 'fileChunk' => $request->file('fileChunk'), 'chunkIndex' => $request->integer('chunkIndex'), 'totalChunks' => $request->integer('totalChunks'), 'fileSize' => $request->integer('fileSize'), 'uploadId' => $request->input('uploadId'), 'fileName' => $request->input('fileName'), 'mimeType' => $request->input('mimeType'), ]); // ['completed' => bool, 'file' => [...]]
Security
- Type/extension/size validation.
- Checking the actual MIME (whitelist).
- CSRF (for `web') / Bearer (for API).
- Access via middleware (authorized users by default).
- CORS/Headers โ configure at the application level.
- Regular cleaning of temporary/deleted data on a schedule.
Performance
chunk_size
is configurable (1 MB by default). A larger chunk means fewer requests, but higher risks of retransmission; a smaller chunk is more resistant to network failures.- Parallel sending of chunks on the client is possible (turn it on with caution, given the limitations of the server).
- For large files, consider
post_max_size
,upload_max_filesize
, and reverse-proxy limits.
FAQ
Is it possible to download multiple files at the same time? yes. The widget supports queuing and (if desired) concurrency.
How do I change the disk/folder?
config/file-uploader.php
โ disk
/ directory
. For S3/CloudFront, see the integration section.
How do I get a CDN link?
For a public CDN, specify AWS_URL
(CloudFront domain) and use Storage::url()'. For a private CDNโ implement a
public_url_resolver` with a CloudFront signature (example above).
How can I disable authorization?
Change the middleware' (for example,
['web']`) or leave it empty โ only if it is safe to do so.
Troubleshooting
- 415/422 โ check the MIME/extensions and `max_size'.
- 404 on links โ check the
storage:link
and the disk/directory configuration. - CSRF โ pass
_token
or use Bearer. - Build failed โ make sure that all chunks are received (indexes are continuous) and the size is the same.
CI / QA / Coding Style
- CI: GitHub Actions โ tests ('phpunit
/
pest'), static analysis (phpstan
), linting (pint
). - Coverage: Codecov.
- Style: PSR-12, Laravel Pint.
Roadmap
- Progress bar on file and shared.
- Parallel loading of chunks + auto-resume.
- Filters/search through the file list.
- Additional locales.
- Wrappers for Livewire/Vue.
Contributing
PR/Issue are welcome. Before shipping:
- Cover new functionality with tests.
- Follow the code style and SemVer.
- Update
CHANGELOG.md
.
License
License Apache-2.0. See `LICENSE'.
Credits
- Package: xakki/laravel-file-uploader
- Namespace:
Xakki\LaravelFileUploader
- Author(s): @xakki