proside/file-manager
Reusable file manager for Laravel 10/11/12 built in Livewire v4: directory tree, grid/list views, uploads, drag & drop, trash with restore, previews, search, multi-disk support and a file picker component. No iframe or external service.
Architecture fit
This is a conventional Laravel package with an unusually clean boundary. The surface area is small and legible: a service provider (auto-discovered), one Livewire component, a handful of Blade partials and components, a FileManagerService for filesystem operations, a MediaController serving media over a named route, and one Artisan command (file-manager:prune-trash). There is no second runtime, no external service, no token exchange.
The architectural win versus the legacy Next.js/React implementation is that the entire class of failure modes around the iframe + postMessage + JWT RS256 + .pem key rotation + CORS + reverse-proxy disappears. Authorization collapses into Laravel's existing session and middleware, and filesystem access is confined server-side by PathGuard. For a server-rendered Blade/Livewire application, this is close to the ideal shape for the problem — a file manager is fundamentally a stateful, permissioned view over a filesystem, and Livewire models that more directly than a decoupled SPA.
The architectural misfit is equally clear: if the target application is Inertia/React/Vue or API-first, embedding a Livewire v4 component means running two frontend paradigms in one product, and the <x-file-manager::picker> drop-in value proposition evaporates because there are no Blade forms to drop it into.
Integration feasibility
Installation is genuinely four commands: composer require, storage:link, optional vendor:publish, and a Tailwind content entry. Package discovery removes the usual provider/route registration chores. The picker preserves the legacy prop API (input-name, :value, multiple, filter) and writes to a plain hidden input, so existing form requests, $model->update() calls and validation rules keep working — the migration cost is concentrated in the UI layer, not in controllers or models.
Two integration gates are real and must be resolved before any commitment:
content glob or the UI renders completely unstyled in production. This is a build-configuration dependency that fails silently and only at render time — the highest-probability "it worked yesterday" incident in the whole integration.Technical risk
uploads.mimes defaults to null, i.e. all MIME types accepted. root_resolver returning a value equal to the configured root, or null/'', grants full access — a resolver bug is a privilege escalation, not a display bug. The default route (GET /file-manager) must be confirmed to sit behind auth middleware and an application policy; route middleware is configurable, which means it is also misconfigurable.filter="images" is a UI affordance; the hidden input is client-controlled. Any team that treats it as a security boundary ships a vulnerability.originalPath. If two users share a root, or if root scoping overlaps, deleted-item visibility becomes a cross-tenant information leak.route proxies every asset through PHP (authorization per request, higher cost); storage is a direct URL (fast, CDN-able, but bypasses per-request authorization and breaks on private disks). auto hides the decision, which is worse for reasoning about production behaviour.Key questions
tailwind.config.js vs v4 CSS-first configuration)? The documented content glob assumes v3-style config.public, private local, S3? If S3 or private local, media_url must be route or signed URLs, and that decision needs an owner.PathGuard, upload handling and trash restore?upload_max_filesize / post_max_size / Livewire temporary-upload limits relative to uploads.max_size (default 50 MB)? Mismatches here produce user-facing failures that look like package bugs.Stack fit
Best case: Laravel 10.49+/11/12, PHP 8.1+, Livewire 4 already in use or approved, Tailwind compiled by the app, Blade-rendered forms. In that configuration the package is a near-drop-in and the picker alone justifies a pilot, because it replaces ad-hoc upload widgets across every form that attaches a file.
Poor fit: Inertia/React/Vue product, or an application still on Livewire 3 with no appetite for a major upgrade. In both cases, stop at the evaluation — the cost sits outside the feature.
Migration path from the legacy Next.js/React manager
The README's endpoint-by-endpoint mapping table is the migration plan's backbone, and it is unusually complete: api/files, api/files/tree, api/folders, api/rename, api/move, api/delete, api/upload, image/route.js, verify-token and the iframe postMessage each map to a Livewire method, a computed property, or MediaController.
Critically, the stored data does not move. Files remain on the same disk under the same paths; only the presentation and access layer change. That makes this one of the lower-risk migrations available. The residual work is decommissioning, not data movement: removing FILE_MANAGER_URL, the RS256 .pem keypair, the token-verify route, the CORS/proxy entries, and the iframe host page. Those decommissioning steps should be explicit tickets, not cleanup — stale JWT keys and a live external file service left reachable is a standing security finding.
Two configuration traps in the path: the defaults (root => conteudos, trash => apagados) are assumptions about the existing filesystem layout, not placeholders. If the legacy deployment used different roots, every path must be set via FILE_MANAGER_ROOT / FILE_MANAGER_TRASH or navigation will open onto an empty tree that looks like a broken install.
Compatibility
media_url mode determines whether S3 actually works under authorization. Resolve this before enabling a non-public disk.file-manager.media and the full-page route must be checked explicitly, not assumed.Sequencing
media_url mode, and whether the full-page route is exposed at all or the component is embedded only.content glob (or publish prebuilt CSS if the app does not compile Tailwind), run storage:link.FormRequest, exercise the file-manager-selected listener if live preview is needed.auth + policy middleware, uploads.max_size and uploads.mimes set to allowlist rather than null, PHP upload limits aligned.root_resolver for tenancy before exposing the manager to more than one user population. Write tests for the escalation cases: resolver returning null, returning '', returning the config root, returning an array with overlapping roots, and traversal attempts above the resolved root.file-manager:prune-trash — daily, in the same change set as the route, not after.Maintenance
Assume you will maintain this. Zero stars and zero dependents means no downstream bug-finding, no third-party patches, and a single point of failure in the maintainer. Practical posture: pin an exact version (not a caret range), mirror the repository internally, and review the diff of every upgrade as if it were internal code. The package's own documentation quality — README, a BACKOFFICE-INTEGRATION.md describing a real per-user scoping integration, and a published release — is materially better than its adoption metrics suggest, which lowers the cost of owning it.
The Tailwind content glob is a permanent maintenance liability: it lives in the application's build config but refers to a vendor path. A package upgrade that relocates views, or a build-config refactor that drops the line, produces an unstyled UI that no PHP test will catch. Add a smoke check to the release checklist.
Publishing views, lang files or assets creates a fork inside your own repo. Default to not publishing; publish only the config and only when defaults must change.
Support
No community, no SLA, no issue-tracker signal to triage against. Internal support means an internal owner with the authority to patch a vendored copy. Debugging information is favourable: it is Blade and Livewire inside your own application, so stack traces land in application logs and standard Livewire debugging applies — no black-box external service, no cross-origin token failures. Ramp-up for a developer already fluent in Blade and Livewire is a day; the genuinely hard part is understanding PathGuard and root_resolver semantics well enough to be confident in the tenancy boundary.
Scaling
Livewire's per-interaction round trip is the cost model. The directory tree lazy-loads per node and each navigation re-lists the directory, so behaviour degrades with directory cardinality, not with user count. Tens of files per folder is comfortable; thousands of files in one folder, or a deeply expanded tree, will produce slow renders and heavy listing calls. On S3 this is worse than on local disk because every render is API calls (and cost), not readdir.
media_url => route proxies every image and video byte through PHP: correct for private disks and per-request authorization, but it puts gallery traffic on the application tier. storage moves that to the web server or CDN at the cost of per-request authorization. Choose per disk, and revisit if a grid view with many thumbnails becomes a hot path.
Upload throughput is bounded by uploads.max_size defaulting to 50 MB, but the effective ceiling is whichever of upload_max_filesize, post_max_size and Livewire's temporary-upload configuration is lowest. Align all of them or users get opaque 413/422 responses.
Failure modes
content glob missing or stale after a view path change. Silent, cosmetic, and the most likely first incident.auth plus a policy. Highest-severity, lowest-effort-to-prevent.root_resolver returning null, '', or the config root grants full access. Needs an explicit test, because the failure is silent and looks like correct behaviour in a happy-path demo.trash_retention_days is decorative without the scheduled command; apagados grows until the disk fills. The legacy implementation's 60-second-in-code-versus-30-days-in-docs bug is documented as fixed here, but only scheduled pruning makes the fix operational.media_url => storage against a non-public disk yields 403s or broken images, typically first noticed in production.originalPath; overlapping or shared roots leak deleted-file metadata between users.null by default, so permissive) combined with PHP limits produces generic validation errors.Ramp-up
Low for the mainstream case: a Blade/Livewire developer can read the config, wire Tailwind, and drop the picker into a form in half a day, with the README's Next.js→Livewire mapping table serving as the mental model. Medium for the full-page experience, where route middleware, upload limits and media_url must be decided rather than defaulted. High for multi-tenant scoping: root_resolver plus PathGuard plus shared-trash filtering is the one area where a competent developer can ship a plausible-looking implementation with a real authorization hole, and it deserves a written design note, a reviewer who did not write it, and a test suite covering the escalation cases before it reaches any shared environment.
How can I help you explore Laravel packages today?