qcod/laravel-imageup
Laravel ImageUp is a trait that adds automatic image upload, resizing, and cropping to Eloquent models with extensive customization. Just mark image fields on your model and it handles uploads, stores paths, and deletes old images automatically.
Architecture fit. qcod/laravel-imageup is not a full media library — it is a trait (HasImageUploads) plus a field-options DSL that attaches image/file handling to Eloquent models. Three design decisions define the architectural fit:
Model::saved(). Persisting a model can therefore write to disk, resize an image, and delete the previous file. This is convenient for CRUD scaffolding but couples persistence to HTTP request state and filesystem mutation.request() for a same-named field (or a renamed file_input). Models become request-aware, which is the largest architectural compromise in the package.$imageFields / $fileFields arrays carry dimensions, crop, disk, path, validation rules, filename overrides, and before_save/after_save hooks. Hooks are resolved through the Laravel IoC container (class-based) or accepted as closures, so custom logic (watermarks, blur, derivative generation) can be injected without forking.Fit is strong for admin panels, CMS entities, profile/avatar fields, and internal tools where an Eloquent model maps 1:1 to a form. It is a weak fit for API-first services, event-sourced or queue-driven pipelines, multi-variant responsive image delivery, or anything requiring deterministic, testable persistence.
Integration feasibility. Composer-only install, auto-discovered service provider, optional publishable config/imageup.php. Required model changes are small (trait + one static property), controllers stay untouched if you accept auto-upload. php artisan storage:link is the only other prerequisite. Migration is a VARCHAR column per image field plus the URL-accessor swap in views. This is realistically a one-to-two-day change for a small set of models.
Technical risk.
ImageManager, resize/crop signatures, encoder quality). The dataset lists the latest release as 2026-08-17; verify on Packagist that the tag actually moved the intervention/image and illuminate/* constraints, because a stale constraint will cap you at an older Laravel/PHP combination.saved() update are not atomic. A failure between them yields orphaned files or rows pointing at missing paths. No compensating cleanup is documented.uploadImage() calls; this must be reconciled with FormRequest validation rather than replacing it.{field}UploadFilePath() magic method naming for filename overrides is undiscoverable and silently inert if misspelled.Key questions.
composer.json constraints for illuminate/support, intervention/image, and PHP, and do they include our target Laravel version?Model::saved() fire on every save (including unrelated attribute updates), and does auto-upload mis-fire on non-form saves such as seeders, imports, or queued jobs where a stale request() exists?delete() — are image files removed, or do we inherit orphan cleanup? (Documented methods are per-field deletion only.)before_save/after_save hooks invoked for manual uploadImage() calls, and do they run before or after the DB path write?rules string run through the Laravel validator with the same semantics as a FormRequest, including image, mimes, max (KB) and dimension rules?Stack fit. Requires Laravel with Eloquent, the Filesystem abstraction, and the IoC container — a native fit for a conventional Laravel monolith. Works with any configured disk (public, local, s3, etc.), so S3/R2 offload is available for storage even though processing stays local. Assumes Blade/server-rendered views or an SPA that posts multipart form data with field names matching the model columns.
Migration path.
composer.json.config/imageup.php; set upload_disk, upload_directory, and decide auto_upload_images globally (recommend false globally, opt in per field).VARCHAR(2048)-class columns via migration; backfill existing image paths so imageUrl() resolves legacy assets.$imageFields to a single low-risk model (avatar/logo), with explicit rules, explicit width/height/crop, and an explicit path.imageUrl()/imageTag().Compatibility. Three boundaries to validate before committing:
storage:link on local; for S3, confirm the package writes via the Laravel disk and does not assume a local path when deleting prior files.Sequencing. Constraint verification → pilot model in a staging environment with representative uploads (large JPEG, PNG with alpha, malicious SVG, oversized file, non-image renamed .jpg) → confirm behavior of automatic old-file deletion against our retention policy → wrap all writes behind a thin application service so the package can be replaced without touching controllers → roll out per model behind a feature flag → only then evaluate queue offloading (defer processing by disabling auto-upload and calling uploadImage() from a job).
Fallback. If constraints or maintenance stall, the substitution cost is low: spatie/laravel-medialibrary (heavier, conversions + queues + multi-variant) or a bespoke ~150-line service around Intervention Image. Keeping our own service-layer wrapper preserves that option.
Maintenance. Single-maintainer, MIT, readme/changelog/releases only — no formal support contract. Budget for periodic upstream checks: Intervention and Laravel majors move faster than this package's cadence, so expect to either fork, pin, or replace at some point. The public API surface we depend on is deliberately small (trait, field options, uploadImage, imageUrl, deleteImage), which makes a future fork tractable. Low opportunity score and zero recorded dependents mean we should not assume community pressure will fix our issues.
Support. No vendor SLA; internal ownership must sit with the application team. Support load concentrates in three recurring areas: "image didn't update" (auto-upload disabled/misconfigured, or file_input name mismatch), "broken image" (disk/path or missing storage:link), and "upload failed" (validation rules or PHP memory). Document these three runbooks explicitly.
Scaling.
max: rules, dimension pre-checks, and PHP memory_limit sized for the largest accepted image.storage/ against DB columns per image field.path or setImagesField() at runtime, but the default single upload_directory invites cross-tenant path collisions if not explicitly partitioned.imageUrl() returns one URL for one stored file. Responsive srcset, WebP/AVIF derivatives, and cache-busting must be built on top (using after_save hooks or a separate pipeline).Failure modes.
| Failure | Symptom | Blast radius |
|---|---|---|
| Disk write succeeds, DB save fails | Orphaned file on disk | Storage bloat, no user-visible error |
| DB save succeeds, disk write fails | Row points to missing file | Broken image in UI |
| Old file deleted before new file confirmed | Permanent image loss | Data loss, no rollback |
Custom {id}-cover.jpg naming |
Overwrites + stale CDN/browser cache | Users see old image |
| Non-form saves (seeders, imports, jobs) with ambient request state | Unexpected uploads or silent no-ops | Corrupted data during batch operations |
| Oversized image | PHP memory exhaustion / 500 | Failed save, possible partial persistence |
before_save hook throws |
Save aborted after validation | Inconsistent state if model partially written |
| SVG or polyglot upload with permissive rules | Stored XSS via public disk | Security incident |
Each of these is manageable, but the default configuration is the unsafe end of the spectrum: auto-upload on, prior-file deletion on, rules optional, hooks unmonitored. Operational hardening means inverting those defaults.
Ramp-up. A Laravel-fluent developer is productive in under an hour — the README is complete for the happy path and the field-options table is self-documenting. The nontrivial learning is concentrated in the implicit behaviors: trait-driven save hooks, request coupling, the {field}UploadFilePath convention, hook ordering, and the fact that manual uploads are silently overwritten on the next save unless auto-upload is disabled. Those belong in a short internal ADR, not the upstream README.
Verdict for the record: adopt for internal, form-driven, single-image-per-field use cases with hardened defaults (auto-upload off, explicit rules, explicit disk/path, retention-aware deletion, service-layer wrapper). Do not adopt where atomicity, asynchronous processing, multi-variant delivery, or audit-grade retention are requirements — the cost of building those on top exceeds the cost of a purpose-built pipeline.
How can I help you explore Laravel packages today?