laravel-enso/files
File management package for Laravel Enso that adds uploads, browsing, and organization of files and folders, polymorphic attachments, image processing, and ready-to-use Vue components for handling files in Enso applications.
This package is an add-on inside the Laravel Enso ecosystem — it is not a standalone upload library and expects Enso core (laravel-enso/enso, which pulls in Structure, Forms, Actions, etc.) to be installed. If you are not already running Enso, treat this as a reference implementation rather than a dependency you can drop into a generic Laravel app.
Minimal steps:
composer require laravel-enso/files
php artisan migrate # creates the polymorphic `files` table
php artisan storage:link # files are resolved through Storage, not raw paths
Where to look first, in this order:
config/files.php (or the enso.files block) — disk, size limits, mime filters, image options.src/Models/File.php — the schema shape (fileable_type, fileable_id, type, path, original_name, mime_type, size, is_public).src/Traits/Fileable.php — the trait you add to your own models.src/Services/* and any Actions/ folder — the upload and delete flows you should call instead of touching the model directly.Your first useful case: attach an avatar or attachment to an Eloquent model.
use LaravelEnso\Files\Traits\Fileable;
class Post extends Model
{
use Fileable;
}
$post->upload($request->file('image'), type: 'cover');
$post->file('cover'); // first file of that type
If the API surface differs in your pinned version, the trait is the single file to read to see the actual method names.
Always upload through the package service, not $file->store(). The service is what wires original_name, mime_type, the type slot, and the storage disk consistently. Hand-rolled store() calls produce rows the rest of Enso cannot interpret.
Use type as a slot key. A model typically wants several distinct files — avatar, cover, attachment. Enso's file UI and file($type) helper assume this, so pick a stable naming convention and stick to it.
Upload from the backend UI via Enso actions. In an Enso form, the file input is a Vue component bound to a backend action that receives the fileable_type/fileable_id and returns the persisted file payload. Don't bypass it with a bespoke controller unless you also replicate the response shape the JS layer expects.
Validate with a FormRequest first, then upload. Enso's file uploader is convenient but the package will happily store a 40 MB video if your config allows it. Validate mime and size in the request, and mirror the same limits in config so the front end shows the right message.
Deletion must be explicit. Files are a polymorphic child table; deleting the parent model does not automatically clean up files rows unless your migration uses onDelete('cascade') and the driver enforces it, or you add an observer. The safe pattern:
class PostObserver
{
public function deleted(Post $post): void
{
$post->files->each->delete(); // deletes row + storage object
}
}
Use it for image-heavy resources sparingly. If you need thumbnails, resized variants, or CDN transformations, confirm whether your version of the package does image processing at all — in many Enso versions that lives in a separate image package or is left to you.
Config is the integration point. Disk selection, public vs private visibility, allowed mimes, and max size all flow from config into the service. Change config rather than overriding the service class.
morphMany plus Flysystem is a tenth of the weight.composer.json and test on upgrades; don't assume ^ is safe.asset() or a plain URL. If you switch is_public or the disk after the fact, existing rows keep the old path and break — plan visibility up front.Relation::enforceMorphMap(), the fileable_type column stores your alias, not the FQCN. Any manual query against files must use the alias.files rows whose created_at is old and whose fileable_id is null (or whose parent no longer exists), and pair it with storage pruning.Storage and direct filesystem paths. The package resolves through Storage::disk(config(...)). If you write directly with file_put_contents the row and the disk diverge, and ->delete() will no-op on storage without warning.files table? (2) path matches what's on the disk? (3) storage:link still valid? (4) correct disk from config? (5) is_public matches the URL you're generating?File model and tune config, but the service layer is opinionated. If your needs diverge (virus scanning, S3 multipart, signed URLs with TTLs), write a decorator around the service rather than editing the vendor package.type field is a string, not an enum. Nothing prevents avatar and avatars coexisting. Keep a constants class per model and use it in both backend and front end.How can I help you explore Laravel packages today?