van-ons/laravel-attachment-library
A Laravel library for attaching files to Eloquent models. Install via Composer, run the install command to add migrations and assets, then use the HasAttachments trait on any model to link and retrieve attachments through a simple relationship.
Architecture fit. The package is a thin, Laravel-native layer rather than a framework of its own: a HasAttachments trait, a first-class Attachment Eloquent model, an AttachmentManager, published migrations/assets via attachment-library:install, and a Glide-backed route for serving and resizing images. It uses standard idioms (Composer, service provider, traits, Eloquent relationships, filesystem disks) so the coupling to our codebase is shallow — the trait and the attachments() relationship name are the only things that leak into domain models. Two design commitments matter more than the README implies:
$model->attachments()->attach($attachment) implies a shared, reusable media table. That is the right model for a CMS or content platform (one asset reused across records) but it forces us to own lifecycle semantics the README does not specify: what happens to an Attachment row and its blob when the last owning model is deleted? Orphan reclamation is our problem, not the library's.Attachment route parameter, preset resizing endpoint added in v1.2.2, fit/crop semantics, focal point in v1.2.0) means the package inserts public HTTP surface area into the app. That is convenience for a marketing site and a security/compliance question for anything with private documents. We must confirm whether the serving route can be authorized per-request or whether we need to bypass it and serve through our own controller/policy.Integration feasibility. Low-to-moderate. Composer require plus one artisan command gets a working prototype in under a day on a greenfield install. The realistic cost sits in the surrounding decisions: disk strategy (local vs. S3 — S3 support arrived in v1.2.9, remote disks in v1.0.0), image variant/CDN strategy, authorization, and backfill of any existing uploads. The library deliberately does not cover upload handling, validation, virus scanning, or tenancy — we supply all of that. Treat it as "Eloquent media plumbing + image endpoint," not as a media platform.
Technical risk. The dominant risk is adoption and stewardship, not code quality. Three GitHub stars and zero dependents means effectively no external production evidence and no third-party troubleshooting corpus; our team would be among the earliest outside users. Mitigating factors are meaningful: MIT license, an identifiable corporate maintainer (Van Ons) with a consistent release cadence across ~20 versions, changelogs that read like real production fixes (missing files on disk, missing extensions, null image sizes, Glide performance, stale docblocks), and a dependency footprint that is mostly Laravel + league/glide. That pattern — many small hardening fixes — is what an internally-extracted package looks like, which is reassuring for durability and slightly less reassuring for API stability. Secondary risks: (a) route collisions and global route behavior (see the v1.2.8 fix preventing Glide preset routes from matching all long image paths); (b) silent divergence if we fork; (c) version-pinning friction against Laravel major upgrades.
Key questions.
AttachmentManager stable/public API for file replacement (v1.1.0) and insertion of missing files (v1.2.1), or internal?Stack fit. Best fit is a Laravel 10/11+ application already using Eloquent and Storage, with a content-shaped domain (pages, articles, products, listings) where the same asset is legitimately reused across records. Poor fit where attachments are strictly per-record, tenant-isolated blobs with regulatory retention rules — in that case the shared-media model becomes a liability and the package's value collapses to "a migration and a trait," which we should just write ourselves in a day.
Migration path. Greenfield or additive-only: install, publish migrations, add the trait to one or two models, store files via Storage, create Attachment records through AttachmentManager, link via attachments()->attach(). For existing uploads, plan a one-off backfill job (insert Attachment rows, copy blobs to the target disk, link to owners) and keep the legacy columns read-only until parity is verified; the v1.2.1 "insert missing files" behavior suggests drift between rows and disk is a known operational reality, so the backfill must be idempotent and re-runnable. Avoid trying to migrate every model at once — the trait is cheap to add but the lifecycle policy is not.
Compatibility. Verify the pinned Laravel/PHP range against our runtime before committing; pin exact versions in composer.json (not ^) for the first two minor cycles and use Dependabot/PR review rather than auto-merge. Because the Glide route is app-global, diff php artisan route:list before and after install on a staging environment and check for conflicts with existing image/CDN routes. Confirm the Attachment model and table names don't collide with anything already in our schema — attachments is a popular name, so on a brownfield app this is a real blocker.
Sequencing.
Maintenance. Ongoing cost is modest but non-zero: dependency bumps (Glide, Laravel), route-table hygiene, and our own orphan-cleanup code. Budget roughly 1–2 engineer-days per quarter plus the initial 3–5 engineer-days for the integration above. The bigger commitment is ownership of a fork contingency — with 3 stars and 0 dependents, we should assume we may be the largest consumer in the ecosystem. Mitigate with a shallow integration (trait on few models, no exposure of AttachmentManager throughout the codebase), an internal ADR, and a habit of opening upstream PRs rather than patching in place.
Support. No vendor SLA, no commercial support tier, no Stack Overflow corpus. Support means: our own on-call, reading the source when issues arise, and filing GitHub issues with a maintainer whose responsiveness is unverified from outside. This is acceptable for internal CMS/media features; it is not acceptable for a revenue-critical path with a hard uptime target unless we are prepared to own the code.
Scaling. Two distinct concerns. Storage and I/O scale fine — remote disks and S3 are supported, blobs live outside the DB. Image transformation is the bottleneck: on-demand Glide resizing is CPU- and memory-heavy, and the changelog's repeated performance work (v1.2.5 optimizer, v1.2.7 null-guard on unreadable files, v1.2.8 route matching) signals this area is where the sharp edges live. For any meaningful traffic, put a CDN in front of the serving route (or serve pre-generated variants) so Glide is hit once per variant rather than once per request, and cap variant permutations to a reviewed preset list rather than allowing arbitrary query parameters. Database-side, the polymorphic pivot adds joins per listing page — eager-load attachments and index the pivot's morph columns; watch for N+1 in admin screens.
Failure modes.
Attachment exists but the blob is gone. Partly handled (v1.3.1 fixed an error when a file is missing from disk; v1.2.3 fixed missing-extension handling), but we need a health check and a user-facing fallback.route:list and by integration tests that assert non-attachment URLs still resolve.Ramp-up. Low for any Laravel developer: the API is one trait and one relationship, and the docs (README + docs/) are adequate. Half a day to productive use; the learning curve is in the unwritten parts — lifecycle policy, authorization, caching, and backfill — which are ours to document. Recommend a short internal runbook covering: how to add an attachment, how to serve private files, how to purge orphans, how to bump the package, and the fork-and-patch procedure. Verdict: a reasonable, low-cost buy for Laravel content applications, contingent on the serving-route authorization answer; escalate to build-internally if that answer is unsatisfactory or if attachments carry retention/compliance weight.
How can I help you explore Laravel packages today?