wirelab/downloads-module
wirelab/downloads-module is a Laravel package for managing downloadable files and tracking download activity. It helps organize downloads, control access, and integrate file delivery into your application with minimal setup. Suitable for projects that need a simple downloads area or file distributio...
Install the package with Composer and publish its config:
composer install vendor/downloads
php artisan vendor:publish --tag=downloads-config
Look at config/downloads.php first — the disk, base path, response type (inline vs attachment), and any route/middleware options all live there. When upgrading to 1.4.2, pay particular attention to the path/folder setting: the package no longer restricts downloads to one configured folder, so that key now acts as a default rather than a hard boundary. If you previously relied on it as a whitelist, re-check your code before deploying.
The fastest first use case is serving a private file through a controller:
Route::get('/invoices/{invoice}/pdf', function (Invoice $invoice) {
return Downloads::file($invoice->pdf_path);
})->middleware('auth');
Because 1.4.2 removes the folder constraint, $invoice->pdf_path can point anywhere on the disk — a storage/app/private/invoices file, a tenant-specific folder, or an object on an S3 disk — without extra configuration.
Stream large files instead of loading them. Use the package's streaming response so a 200 MB export doesn't get buffered into memory:
return Downloads::disk('s3')->download($path, $filename);
Keep authorization in your own layer. The package returns bytes; it does not decide whether the current user may have them. Wrap every download route in auth plus a policy or Gate::authorize() check against the underlying model, and derive the path server-side rather than accepting it from the request.
Use per-disk calls for multi-tenant or multi-bucket setups. With the folder restriction gone in 1.4.2, the clean pattern is one download route parameterised by disk and a trusted, policy-checked path:
return Downloads::disk($tenant->disk)
->download($tenant->pathFor($document), $document->name);
Prefer inline responses for previews and attachments for exports. Set the disposition at the call site so a single config default doesn't force PDFs to download when you want them rendered in the browser.
Combine with signed URLs for email links. Generate a temporary signed route to your own controller, then let the package stream the file. This keeps expiry and authorisation logic in Laravel rather than in the package.
Test with Storage::fake(). Put sample files on the fake disk, assert assertDownload() on the response, and assert that unauthorised users receive a 403. This is the most reliable way to catch a path that used to be inside the configured folder but now isn't.
realpath() against an allowed prefix) whenever the path is influenced by user input or database content you don't fully control.Downloads::file($request->path) now has a much larger blast radius than before. Whitelist extensions and reject .. segments.php artisan config:clear. Stale cached config is the usual cause of "it still can't find the file."Downloads::file() typically expects a path relative to a configured disk root; Downloads::response()-style helpers may take a full path. Mixing them up produces "file not found" errors that look like permission problems.Content-Disposition; pass a plain ASCII fallback and let the package's filename handling do the rest.How can I help you explore Laravel packages today?