sehrgut/laravel-attachments
A Laravel package for attaching files to Eloquent models. It offers a simple, reusable way to manage uploads and storage and to associate one or many files with your records through clean, expressive APIs, making it easy to add file and media support to any model.
Read the package status first. This package is archived and has effectively no community (2 stars, 0 dependents). Treat it as a reference implementation or a starting point to fork, not a dependency you install and forget. Before writing any code, open the repository and read src/, config/, and database/migrations/ — there is no meaningful third-party documentation or Stack Overflow coverage, so the source is the documentation.
Install (or better, vendor it into your project so you control it):
composer require sehrgutesoftware/laravel-attachments
Publish assets and run migrations:
php artisan vendor:publish --provider="SehrgutSoftware\Attachments\AttachmentsServiceProvider"
php artisan migrate
Add the package trait to any Eloquent model that should own files:
use SehrgutSoftware\Attachments\Traits\HasAttachments;
class Post extends Model
{
use HasAttachments;
}
First use case — attach an uploaded file on create. The canonical flow is: accept an UploadedFile in a controller, save the model, then hand the file to the model's attachment API:
public function store(StorePostRequest $request)
{
$post = Post::create($request->validated());
$post->attach($request->file('cover'));
return redirect()->route('posts.show', $post);
}
Confirm the file landed where you expect. Check the published config/attachments.php for the default disk and base path, then verify on disk (storage/app/...) and via the model relation ($post->attachments). Getting storage wiring right on the first attempt is the main onboarding hurdle.
Attach after persist, not before. Attachments are tied to a model via a polymorphic relation, so the parent must have a primary key. In practice: create/update the model inside a transaction, then attach files. If you need atomicity across DB + disk, wrap in DB::transaction() and clean up files in a catch.
Request-to-model mapping pattern. For multi-file uploads, loop explicitly rather than guessing package behavior:
foreach ($request->file('attachments', []) as $file) {
$post->attach($file);
}
Replacement workflow. "Replace the cover image" is more common than "add another." Implement it as detach-then-attach, and delete the old file from disk in the same step so you don't accumulate orphans:
$old = $post->attachments()->where('collection', 'cover')->first();
$post->attach($request->file('cover'), ['collection' => 'cover']);
$old?->delete(); // or ->deleteFile() depending on the package's API
If the package doesn't support collections out of the box, add a collection column in your own migration and scope the relation accordingly — that's the pragmatic extension point.
Polymorphic reuse. The trait is designed to be dropped on multiple models. Keep a single Attachment model and one storage convention (attachments/{model}/{id}/{uuid} or similar) so cleanup jobs and backup tooling work uniformly.
Serving files. Never expose the storage path directly. Serve through a controller action that authorizes the request, then return a streamed download:
public function download(Post $post, Attachment $attachment)
{
$this->authorize('view', $post);
abort_unless($attachment->attachable->is($post), 404);
return Storage::disk($attachment->disk)->download($attachment->path);
}
Testing pattern. Use Storage::fake('attachments') (or whatever disk you configure) in feature tests, then assert attachment rows exist and files were written. Because the package is archived and untested against newer Laravel versions, your test suite is your regression safety net — write tests for attach, delete, and download before upgrading Laravel.
Integration tip. If you're building something long-lived, wrap the trait behind your own FileService / Attachments interface so swapping to spatie/laravel-medialibrary or a homegrown model later is a one-file change, not a grep-and-replace across the app.
Storage, File, or UploadedFile before relying on it.attach, addAttachment, or files. Don't assume — grep -r "public function" src/ before you write controllers.attachments table doesn't exist after composer require, you skipped vendor:publish.config/filesystems.php, or storage:link not being run when the package returns public URLs. Check both.deleting event handlers for files are unreliable across package versions. Add an explicit deleting observer on each owner model that deletes attached files, plus a scheduled attachments:prune command you write yourself for files with no DB row.mimes, max, dimensions for images) and always authorize downloads — never trust a raw attachment URL.UploadedFile::isValid() return true (check php.ini upload limits)? (5) did the DB insert actually commit before the file write?Attachment model (custom casts, accessors for URLs), and add your own collection or metadata JSON column rather than forking the package's schema.How can I help you explore Laravel packages today?