Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cabinet Laravel Package

capevace/cabinet

Cabinet is a Laravel package for file and media management, offering an Eloquent-backed file manager with uploads, storage disk integration, and folder organization. It makes adding media handling and file browsing to your Laravel apps simple and clean.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require capevace/cabinet
php artisan vendor:publish --provider="Capevace\Cabinet\CabinetServiceProvider"   # config, migrations, assets
php artisan migrate
php artisan storage:link

The two files you will touch first are config/cabinet.php and the migrations. The config defines buckets — each bucket is a named file source backed by a driver. A minimal disk-backed bucket looks like:

// config/cabinet.php
'buckets' => [
    'uploads' => [
        'name'   => 'Uploads',
        'driver' => 'disk',
        'disk'   => 'public',
        'root'   => 'uploads',
    ],
],

The first use case to validate the install is: open the Cabinet UI (default route registered by the package, usually /cabinet, configurable via the config's route prefix), upload a file into uploads, and confirm it appears under storage/app/public/uploads. Once that round-trip works, wire the UI into your app via the Blade component (<x-cabinet::cabinet /> / file picker) and start attaching files to models.

For model attachments, add the package's HasFiles concern to the model that owns the files:

use Capevace\Cabinet\Concerns\HasFiles;

class Post extends Model
{
    use HasFiles;
}

$post->files;                    // attached files
$post->attachFile($file);        // or the package's current attach API

Implementation Patterns

One bucket per domain concern, not one giant bucket. Separate avatars, documents, exports, and imports into distinct buckets. It keeps disk roots isolated, makes authorization rules simpler (you can allow the picker on avatars without exposing documents), and means swapping a driver for one domain doesn't touch the rest.

Swap drivers instead of rewriting code. The point of the package is that the same UI/API sits over different sources. A dev environment can point documents at a local disk driver while production points the same bucket at S3 or a spatie/laravel-medialibrary-backed driver via config. Reach for a custom driver when you need files that already live elsewhere (a legacy DB table, an asset CDN, an external API); implement the driver interface, register it, and the picker/browser works unchanged.

Embedding the picker in a form. The typical workflow is: a Blade view renders the file-picker component bound to a bucket; the picked file's identifier is submitted in a hidden input; the controller then attaches that file to the model. Always resolve the incoming identifier through the bucket (Cabinet::bucket('avatars')->find($id)) rather than trusting a raw path from the request — never accept a raw disk path from the client.

Uploading programmatically (imports, jobs, API endpoints). Use the facade for anything not driven by the UI:

use Capevace\Cabinet\Facades\Cabinet;

$file = Cabinet::bucket('exports')->put('reports/2025-09.csv', $contents);

// Later, in a controller:
return redirect()->away($file->temporaryUrl(now()->addMinutes(10)));

Rendering files. Centralise URL generation behind your own helper/DTO so private disks work: for public disks use $file->url(); for private disks always use $file->temporaryUrl(...). If you render URLs directly from Blade, private buckets will silently produce broken links.

Organising with directories. Keep a stable convention ({tenant}/{model}/{uuid}-{name}) for paths. The physical path is your only durable handle across drivers, so treat it as data, not display.

Authorization. Put a Gate::define('browse-cabinet', ...) in a service provider and guard the package's route with middleware you control (typically auth plus your gate). Do not expose the UI's route to unauthenticated users just because it works locally.

Livewire / Inertia interop. The picker is a JS component; if you're in Livewire, listen for its "file selected" event and $wire.set(...) the identifier. If you're in Inertia, the picker works but you'll want the selected ID as a form field so it survives Inertia's navigation model.

Gotchas and Tips

  • Config cache. Editing config/cabinet.php does nothing after php artisan config:cache. Re-cache or clear it — this is the #1 "my bucket doesn't exist" cause.
  • Assets after upgrades. The UI ships prebuilt assets. After composer update, re-run vendor:publish --tag=cabinet-assets --force or you'll see a stale JS bundle with a mismatched API.
  • storage:link is required for public disks. Without it, uploads succeed but URLs 404. Similarly, remember APP_URL matters — generated URLs use it, so a wrong APP_URL in staging breaks the picker's thumbnails.
  • Private disks need temporary URLs. $file->url() returns a plain path on private disks; always branch on disk visibility, or write one urlForFile() helper and use it everywhere.
  • Driver leakage. Not every driver supports every operation (directories, move, rename, streaming). If you implement a custom driver for an external API, handle the "unsupported operation" cases explicitly — throwing early is better than a silent no-op that leaves the UI inconsistent.
  • Identifiers are bucket-scoped. A file ID from avatars is meaningless in documents. Always resolve within the bucket, and validate the bucket name from the request against a hardcoded allowlist — never let a request choose the bucket freely.
  • Eager-load attachments. $post->files in a loop is an N+1. Use Post::with('files')->paginate() on list pages.
  • Testing. Use Storage::fake('public') to avoid needing real credentials, and test the driver contract separately from the UI so a driver swap doesn't quietly break uploads.
  • Extension point for bespoke sources. The driver interface (plus Cabinet::extend(...)/config registration) is where you plug in external APIs. Implement it as a thin adapter class, keep it out of your app's domain code, and unit-test it against a fake HTTP client for API-backed drivers.
  • Debugging uploads. When an upload "succeeds" but no file appears, check in this order: bucket name in the request, the disk's root path, disk write permissions, and finally the queue worker (if you're processing uploads asynchronously). Cabinet's value is the uniform API; when it misbehaves, it's almost always the underlying driver/disk config, not the package.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings