dejwcake/craftable-media
Media library and file management for the Craftable admin panel: upload, browse, organize, and attach images and files to your Eloquent models with a ready-made admin UI, keeping media handling consistent across your Laravel application.
What this actually is (scope of evidence). The package is not a standalone media library. It is a fork of brackets/media (namespace Brackets\Media), part of the Craftable admin ecosystem, and it is a thin wrapper over spatie/laravel-medialibrary. That gives us three layers to reason about: Spatie's media library (data model, conversions, disk abstraction — battle-tested, widely deployed), brackets/media (Craftable-specific ergonomics: fluent MediaCollection rules, request-driven add/update/delete, thumbnails, upload and private-file controllers), and dejwcake/craftable-media (this fork, at 2.0.1, zero stars). The Score: 16 is star-driven and therefore not informative on its own; the decision-relevant signals are the transitive dependency and the CI/QA posture described in AGENTS.md.
Architecture fit. Conventional Laravel service-provider package: binds container services, attaches to Eloquent models via HasMediaCollectionsTrait, persists to configured filesystem disks, and exposes HTTP controllers for upload and private file viewing. Adoption is incremental — one model, one collection — with no imposed architectural pattern. Two design details matter for us: AutoProcessMediaTrait hooks the saving event, so conversions can be triggered by any model write (a coupling point between our write path and media processing); and the package ships its own controllers and routes, which means it owns part of our HTTP attack surface, including a private-file viewer.
Integration feasibility. Composer install, publish/verify migrations, configure a disk. Mechanically easy. The gate is environmental, not structural: the documented toolchain is PHP ^8.5, Laravel 13, PHPUnit 13, Orchestra Testbench 11. Unless our target application is already on Laravel 13, this fork will not resolve — and if we back-port or pin to an older line, we are maintaining a fork of a fork. That is the first thing to establish and it is a hard gate, not a preference.
Technical risk. Low-to-moderate, with one severe supply-chain item.
media table, and conversion machinery belong to Spatie, the exit path is materially cheaper than a bespoke package: we can migrate to spatie/laravel-medialibrary directly and keep the data, disks, and generated URLs largely intact. The Craftable layer is ergonomics, not the schema. This substantially de-risks the "sticky dependency" concern.dejwcake is to us and whether this fork exists because of upstream changes we depend on.brackets/media is unknown and unquantified.declare(strict_types=1), no facades, final readonly, #[Override], phpcs/phpstan/phpmd/phpunit all green as definition-of-done, and CI across MariaDB and PostgreSQL. That is a stronger engineering posture than most zero-star packages. It also includes a documented PHP 8.5.3 segfault workaround around #[Override] on trait methods, which tells you the toolchain is being absorbed at the leading edge — fine for a greenfield app, awkward for a regulated or long-LTS estate.Key questions.
brackets/media and the pinned Spatie major? Can we diff it, and will the fork rebase or is it diverging permanently?Stack fit. This is the decisive constraint. The package targets PHP 8.5 / Laravel 13 — effectively next-generation. Most enterprise estates are on Laravel 10/11 with PHP 8.2/8.3, where this fork will not install. Confirm our runtime before any other work; everything below assumes either a Laravel 13 target or a willingness to accept the fork risk of back-porting. The CI matrix (PostgreSQL and MariaDB, healthchecked compose, dedicated laravel DB user) is genuinely useful evidence that persistence is exercised on both engines — relevant if we standardise on Postgres and are wary of MySQL-only assumptions.
Migration path. Treat as a contained pilot with an explicit go/no-go, not a platform decision.
spatie/laravel-medialibrary directly instead — we would be reimplementing less than the wrapper's surface suggests.saving-hook trait before writing any code. These are the two places where a dependency can hurt us.^2.0) and install in one non-critical environment.media table match our modelling conventions, since that table is the part we keep forever.Compatibility. No breaking changes declared in 2.0.1 (app() → Container::getInstance()->make() in three classes, upgrade action "nothing to do"), and the two idioms resolve the same singleton in a bootstrapped app, so the patch itself is safe. Note a small internal inconsistency worth a question rather than a concern: the conventions document permits app() in models and traits, yet 2.0.1 removed it from ProcessMediaTrait — the changelog and the stated conventions are out of step, which suggests the doc trails the code. The real compatibility exposures are environmental: filesystem drivers and permissions, the image/processing binary for conversions, queue driver semantics, and Octane/long-running-worker behaviour given the container-resolution change. Run our existing suite against the pinned version rather than relying on the release note.
Sequencing. Two independent tracks. Track A: if we are already on Laravel 13, land 2.0.1 as a routine pinned dependency and pilot on one model — days, not weeks. Track B: if we are not on Laravel 13, the wrapper-adoption decision is blocked, and the correct next artefact is a one-page comparison of "adopt Spatie directly" versus "adopt this fork," because the irreversible item — data in the media table and objects on a disk — is identical either way, while the maintainable surface is smaller with Spatie alone.
Maintenance. The QA posture is the strongest signal in the package. A change is only "done" when phpcs, phpstan, phpmd and the test suite are green; tests are organised per-method; the README documents per-database test commands and a full QA one-liner; everything runs in Docker. That is better than most commercial packages and means our engineers can validate a bump locally in minutes. Ongoing cost to us is a pinned-version bump per release plus a periodic re-verification of conversions and disk config — low and largely automatable. The unbounded cost is fork divergence: with no upstream changelog to diff, every bump is a review rather than a read.
Support. The weakest dimension and the one that decides whether this becomes load-bearing. Zero stars, personal namespace, fork-of-a-fork — expect best-effort, single-maintainer, unstated security response times. If this fork exists to carry a patch our product depends on, we own that patch in practice and should say so out loud. Mitigation is cheap here: because the schema and storage layout belong to Spatie, a documented fallback to Spatie directly (or a thin in-house read path over the same media table) is a realistic hedge to scope now rather than during an incident. Write that fallback down before go-live.
Scaling. Read-path cost is negligible — URL generation and collection lookups are cheap. Scaling pressure sits in conversions and storage. Conversions are CPU-bound and must run off-request on queue workers; the saving-hook trait makes it easy to accidentally trigger them inline on a model write, which will appear as request-latency spikes and worker starvation under upload bursts. Size worker capacity to peak upload volume, put a CDN in front of generated URLs so the app is not in the delivery path, and add a reconciliation job early for orphaned objects — rows deleted with files left on disk, or the inverse — which is the predictable long-run storage-growth problem.
Failure modes. (a) Runtime mismatch surfacing as an unresolvable composer graph rather than a clear error. (b) Private-file authorization assumed rather than verified — an access-control defect, not a broken image. (c) Disk misconfiguration or missing credentials degrading silently into broken URLs, invisible until users report it. (d) Conversion failures leaving records pending with no alert. (e) Partial writes on large uploads. (f) Silent divergence after a fork upgrade with nothing to diff against. All are covered by one control: an end-to-end smoke test in CI that uploads, converts, fetches through the private controller as an unauthorised user, and deletes, against a real storage driver.
Ramp-up. Low for anyone fluent in Laravel — trait-plus-service-provider needs no new mental model, and the container-resolution refactor is invisible to callers. Budget roughly half a day for a first integration on a single model, plus a day each if conversions or a non-local object-store disk are in scope. The larger cost is documenting our conventions — disk names, collection names, conversion presets — so the package is applied consistently instead of reinvented per feature. If the team is not already on PHP 8.5, add the toolchain upgrade to the estimate; that, not the package, is the real work.
How can I help you explore Laravel packages today?