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

Laravel Fileable Laravel Package

toneflix-code/laravel-fileable

Laravel Fileable adds simple file attachments to Eloquent models. Upload, store, and associate files with any model through a clean API, with support for multiple files, custom storage disks, and automatic cleanup of orphaned uploads in your Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install

composer require toneflix-code/laravel-fileable

2. Publish the config, migrations, and run them

php artisan vendor:publish --provider="ToneflixCode\Fileable\FileableServiceProvider"
php artisan migrate

If tagged publishing isn't documented, fall back to --tag=config / --tag=migrations (or php artisan vendor:publish and pick the toneflix-code entries interactively). Check composer.json for Laravel auto-discovery — if present, the service provider and facade are registered for you.

3. Add the trait to the model that owns files

use ToneflixCode\Fileable\Traits\Fileable;

class Post extends Model
{
    use Fileable;
}

4. First use case — attach an uploaded file in a controller

public function store(StorePostRequest $request)
{
    $post = Post::create($request->validated());

    $post->attachFile($request->file('cover'), 'covers');

    return new PostResource($post);
}

Where to look first

  • The package README for the exact trait name, method signatures, and config keys — small packages often diverge from their marketing copy.
  • src/ for the trait and any File model. This tells you whether it uses a polymorphic files table, JSON columns on the parent, or a single column.
  • The published migration to see the schema (fileable_id, fileable_type, disk, path, mime, size, collection, or similar).
  • config/fileable.php — this is the contract that everything else reads from.

Implementation Patterns

Trait-driven (automatic) uploads. The "automatic" half of the trait usually hooks saving/saved model events and reads an attribute you assign directly:

$post->cover = $request->file('cover'); // UploadedFile
$post->save();                          // file persisted by the trait

This is convenient in Filament/Livewire-style forms but hides I/O inside save() — see Gotchas.

Explicit attach/detach. Prefer the explicit API in controllers and jobs so uploads are visible in the call graph:

$post->attachFile($file, 'covers');
$post->attachFiles($request->file('gallery'), 'gallery');
$post->detachFile($fileId);

Disk and path control. Pass the disk per call ('s3', 'public') or rely on the config default. Model the destination directory by class, e.g. covers/posts/{id}, so files are easy to find and garbage-collect. Store the relative path in the DB and the disk name separately — never the full URL.

Collections / multiple files per model. Treat the disk/collection argument as the grouping key. On the read side you typically get something like $post->files, $post->files('gallery'), or $post->file('cover') — confirm the accessor names in the trait and wrap them in your own scopes (withFiles) if the API is awkward.

Serialization. Files are meaningless to a frontend without a URL. Add an API resource that maps each file to disk->url($path) (or a signed temporary URL for private disks) rather than exposing raw storage paths:

'cover' => $post->file('cover')
    ? Storage::disk($post->file('cover')->disk)->url($post->file('cover')->path)
    : null,

Validation stays in the FormRequest. The package does not validate — you still own mimes, max:2048, image, etc. Validate before the model is touched so a failed request never writes to disk.

Replacing a file. The safest pattern is attach-new, then detach-old, so a failed replacement never leaves the model with no file. Verify the package deletes the physical file on detach; if not, do it yourself.

Async / queue integration. If the trait's automatic hook fires on save(), uploading inside a queued job is fine, but the UploadedFile must already be moved to a temp path (you can't serialize an UploadedFile). The explicit attachFile() API is easier to reason about in jobs.


Gotchas and Tips

Adoption risk. With 2 stars and 0 dependents, treat this as a small, unvetted package: pin the exact version in composer.json, read the trait source before shipping, and wrap usage behind your own small service or model method so you can swap it later.

Uploads inside transactions. If the trait writes files during saving/saved, a later DB rollback leaves orphan files on disk. Do uploads after the transaction commits (DB::afterCommit) or accept the orphans and add a files:prune command.

Disk misconfiguration is the #1 cause of 404s. Check config/filesystems.php and FILESYSTEM_DISK in .env. For the public disk you must run php artisan storage:link, and remember Storage::url() returns a path relative to APP_URL — behind a proxy or CDN this often needs ASSET_URL or a custom URL resolver.

The "automatic" trait may assume column names. Verify the trait doesn't expect e.g. a cover column on the model. If it does, either add the column or use the explicit attachFile() path instead.

Deletion semantics. Confirm whether deleting the model also deletes its files. The safest route is to add your own deleting observer that calls detachFile() for every file, and use forceDelete awareness for soft-deletes (a soft-deleted post should not lose its cover).

Naming collisions. If the package hashes filenames, you get dedup for free but lose the original name; store it in your own column if the frontend needs it for Content-Disposition.

Testing. Fake the disk and assert on it — this is the cleanest way to verify the package behaves as documented:

Storage::fake('public');

$this->postJson('/posts', ['cover' => UploadedFile::fake()->image('a.jpg')])
     ->assertCreated();

Storage::disk('public')->assertExists('covers/posts/1/a.jpg');

Debugging checklist. In order: (1) config/fileable.php disk value; (2) Storage::disk($disk)->exists($file->path) in tinker; (3) whether the file is on the default disk vs the model's recorded disk; (4) whether storage:link exists; (5) whether the trait's event listener is actually registered (Model::getEventDispatcher()).

Extension points. Look for a configurable File model class, a filename/directory resolver closure, and overridable trait methods (attachFile, pathFor). Overriding these is usually a safer fork-free customisation than patching vendor/.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource