mezcalito/ux-filemanager
A Laravel package that adds a ready-to-use file manager UI for browsing, uploading, renaming, and deleting files and folders on your configured storage disks. Ideal for admin panels, it offers a simple, customizable way to manage files without building CRUD screens.
mezcalito/ux-filemanager is a Symfony bundle (Symfony UX + Stimulus), not a Laravel package — if you're coming from Laravel, expect to drop the familiar Storage/File facade mental model and think in terms of Flysystem filesystems + a Symfony form type + a Stimulus-driven picker UI. There is no Laravel integration; you can only use it in a Symfony app (or port the front-end pieces).
As of 0.1.7 the package supports Symfony 8 / PHP 8.5 alongside the older Symfony lines — still Symfony-only, but actively maintained against the newest framework release.
Minimal setup path:
Install
composer require mezcalito/ux-filemanager
Flex should register Mezcalito\UxFileManager\MezcalitoUxFileManagerBundle and import its routes. If not, add the bundle manually and import the routing file from the bundle (@MezcalitoUxFileManager/config/routes.yaml or similar — confirm with php bin/console debug:router | grep -i file).
Wire up the front-end assets. The bundle ships a Stimulus controller and CSS. In an AssetMapper app, confirm the controller is discoverable/imported in assets/controllers.json or importmap.php; in an Encore app, run your asset build. Without this the modal/picker will render but do nothing.
Configure at least one filesystem. The bundle does not ship a storage backend — it delegates to Flysystem (typically via league/flysystem-bundle). Declare a filesystem in your Flysystem config, then point the file manager at it:
# config/packages/flysystem.yaml (illustrative — verify key names)
flysystem:
storages:
default.storage:
adapter: 'local'
options:
directory: '%kernel.project_dir%/var/storage/default'
# config/packages/mezcalito_ux_filemanager.yaml
mezcalito_ux_filemanager:
filesystems:
default:
storage: 'default.storage'
Always confirm the real schema with:
php bin/console config:dump-reference mezcalito_ux_filemanager
Smoke-test the manager route. Hit the file-manager route (usually /file-manager or similar; verify in debug:router) and try uploading a file through the UI. This proves assets, routing, filesystem config, and write permissions all line up before you touch forms.
Where to look first: config:dump-reference mezcalito_ux_filemanager for the real config tree, debug:router for the manager endpoints, the bundle's Form/FileManagerType.php for form integration, and the assets/ directory for the Stimulus controller (selection events live there).
First real use case: attach an image/document to a Doctrine entity. Add a FileManagerType field to your form, let the user pick or upload via the modal, persist the returned storage path as a string column, and render it later via asset() or a Flysystem URL resolver.
Form-integrated picker (the 80% case). Use the bundle's form type instead of a raw <input type="file">. It returns a path string relative to the configured filesystem root:
$builder->add('imagePath', FileManagerType::class, [
'filesystem' => 'default',
'label' => 'Image',
]);
Persist that string directly (private ?string $imagePath). Do not store absolute paths or URLs — you want to stay storage-agnostic so you can move to S3/CDN later without a data migration.
Multiple filesystems for public vs. private. Declare e.g. public (web-accessible, local adapter under public/uploads) and private (under var/storage). Pass 'filesystem' => 'private' on sensitive fields. This is the cleanest way to keep PII docs out of the public web root.
Standalone manager page + custom controller. The bundle exposes the browsing UI, but you still own serving files. Typical pattern: a controller action that takes an id/path, validates access with your normal security voters, then stream-downloads via the Flysystem filesystem:
return $this->file($filesystem->readStream($path), basename($path));
This keeps authz in your app instead of relying on the bundle's UI.
Rendering after selection. For private files, expose a signed/authorized route. For public ones, use Flysystem's public URL ($filesystem->publicUrl($path)) or asset('uploads/' . $path). Pair with LiipImagine (or a Flysystem thumbnailer) for responsive image variants — the bundle is a manager, not an image pipeline.
Wiring the picker into non-form UI. Use the Stimulus controller directly when you build custom admin tooling (e.g., a repeater of gallery items). Attach the controller to a container, listen for the selection event emitted by the bundle's controller, and hydrate hidden inputs. Read the controller source to confirm the event name/payload — it's the extension point most day-to-day customization hangs on.
Uploads and replacements. Let the manager own uploads into the configured filesystem; your app only stores references. On entity delete, either null out the column or add an onFlush/event listener that calls $filesystem->delete($path) — there's no automatic GC in the bundle, so orphan cleanup is your job.
Security integration. Wrap the manager routes in your firewall via access_control:
security:
access_control:
- { path: ^/file-manager, roles: ROLE_ADMIN }
Treat the manager as an admin back office, not a user-facing feature, unless you've audited its authorization model for multi-tenant use.
Storage::disk(), no Blade component, no package:discover. If your team is Laravel-first, budget for the framework mismatch before adopting — the value is the ready-made UX picker, not the storage abstraction (Flysystem does that part).access_control and, if multi-tenant, scope per-user filesystems in a custom controller rather than exposing the shared manager.assets/controllers.json (AssetMapper) or your Encore build output, and look at the browser console for 404s on the controller JS. Newer Symfony UX versions tighten controller/importmap resolution, so re-verify after upgrading Symfony itself.config:dump-reference over blog posts or the README.storage: reference or a nonexistent directory: produces errors only when the UI tries to read/write. Validate by uploading immediately after setup, and make sure the var/ (or public/uploads/) directory is writable by the PHP user./ and don't assume they're URLs. Normalize ltrim($path, '/') before passing to Flysystem to avoid adapter-specific quirks between local and S3.File constraint won't apply here — you're storing a path string, so add a custom validator that re-checks the resolved file's MIME/size). Add collision handling if users upload same-named files into one directory.upload_max_filesize / post_max_size. The bundle can't bypass PHP limits; large uploads silently fail when these are set low.composer update. Still pin the version in composer.json and eyeball the diff between releases before bumping.debug:router (routes exist?), debug:config mezcalito_ux_filemanager (config parsed?), browser console (assets loaded?), var/log/dev.log (Flysystem errors?), and a direct $filesystem->write() in bin/console (storage writable?). Work that order — it isolates routing → assets → config → storage in four steps.How can I help you explore Laravel packages today?