tomatophp/tomato-browser-module
A Tomato PHP admin module that adds a browser for exploring and managing files and media inside your Laravel application, integrating with the Tomato admin panel to give developers a simple built-in file browsing interface.
tomatophp/tomato-browser-module v1.0.0 is the first tagged release, so treat it as a greenfield integration rather than an upgrade — there is no migration path to worry about, but also no battle-tested upgrade history to lean on.
composer require tomatophp/tomato-browser-module
php artisan vendor:publish --tag=tomato-browser-module-config
php artisan vendor:publish --tag=tomato-browser-module-assets
php artisan storage:link
Where to look first, in this order:
config/tomato-browser.php — the published config is the real API surface. Note the disk name, root path, allowed extensions/mimes, and any middleware/permission keys before writing a single line of code.src/TomatoBrowserModule.php (or the package's Filament plugin class) — confirms whether this is a Filament plugin, a plain service provider, or both, and which Filament major version it targets.README.md — for a v1.0.0 the README is the spec; the changelog link carries no migration detail.Register it on your panel:
public function panel(Panel $panel): Panel
{
return $panel
->plugin(\TomatoPHP\TomatoBrowserModule\TomatoBrowserModulePlugin::make());
}
First use case: point it at storage/app/public on the public disk, open the panel, upload a file, confirm it previews and downloads. That single loop exercises the disk resolution, the mime whitelist, the asset pipeline, and the authorization gate all at once — if any of those four are misconfigured, this is where you'll see it.
Scope the browser per panel, not per app. If you run an admin panel and a tenant panel, register the plugin on each with a different root via the config or a ->root() / ->disk() fluent call if exposed. Sharing one global root between panels is the fastest route to cross-tenant file leakage.
Per-user directory isolation via closure. Rather than a static root, resolve the root dynamically so each user only ever sees their own subtree:
// config/tomato-browser.php
'root' => fn () => 'users/' . auth()->id(),
Anything that resolves paths at request time (not at config-cache time) is your friend here; anything baked into a cached config array is not.
Use it as a picker, not just a manager. The highest-value day-to-day pattern is embedding the browser as a file-selection field on an existing Filament resource — a FilePicker form component or a modal action that returns a path string you store on the model. That turns the module from "a file manager I visit" into "the file layer of every resource I build."
Pair with your own model layer. Keep a Media/Attachment Eloquent model that records path, disk, mime, size, and owner, and write to it from a browser event or a small action. Files on disk with no database row become unmanageable within a month; the reverse (rows with no cleanup on delete) leaves orphans.
Authorization belongs in a gate, not in the UI. Define a browse-files / manage-files gate in a service provider and reference it from the config. Hiding toolbar buttons is presentation; the gate is the control. Do this before the first deploy, not after.
Deployment. Add the asset-publish and filament:assets steps to your deploy script alongside storage:link. These are the two steps most often forgotten on a fresh environment, and both fail silently-ish (broken styling, 404s on download).
Testing. Storage::fake('public') plus a Livewire/HTTP test against the browser route is enough to cover upload, delete, and the traversal guard. Worth writing early, because the traversal guard is the one thing you genuinely cannot afford to regress on a v1.0.0 with no security history.
php artisan config:clear — otherwise you'll debug a phantom for twenty minutes... and absolute paths, but verify it yourself with a Storage::fake test before trusting it in production. Never set the root to local disk's base or to your app root.storage:link is mandatory for the public disk to serve files. On Windows/Homestead, symlink creation can fail silently under some permission setups.upload_max_filesize and post_max_size in php.ini will bite you before any package config does. Livewire's temporary upload disk is a separate setting again — check livewire.temporary_file_upload.disk.chmod, in-place rename, and server-side zip are not available; rename becomes copy+delete, and large-directory listing gets slow and expensive. If you're on S3, expect to disable or hide several toolbar actions.composer.json. Custom Blade views also need their paths added to your Tailwind content array, or your overrides will render unstyled in production builds.Gate::before / authorize and confirm what happens in production."tomatophp/tomato-browser-module": "^1.0" — a v1.0.0 with no prior release history gives you no signal about how the maintainer handles breaking changes. Read the changelog before every minor bump for the first few releases.php artisan about for the resolved environment, the Laravel log for storage exceptions (they surface as generic 500s in the UI), and filament:optimize-clear when cached components or views go stale after an update.How can I help you explore Laravel packages today?