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

Media Library For Laravel Laravel Package

roundly-consulting/media-library-for-laravel

A Laravel media library for attaching files to Eloquent models. Upload and store images or documents, generate conversions and responsive thumbnails, organize media in collections, and manage custom properties through a clean, fluent API. Supports local and cloud disks for flexible media management.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and publish. composer require <vendor>/<package>, then publish the config and migrations and run php artisan migrate. The migrations are worth reading: the media table holds a checksum-addressed original path, a disk, and a shared/reference indicator — everything else (dedupe, attach(), copy(), replace()) is a consequence of that row design.
  2. Wire up your models. Add the package's trait/interface to every model that owns media. Do it before writing any tests — the trait is what registers the forceDelete and transaction hooks described below.
  3. Configure disks first, paths second. Set your default disk and any per-collection overrides in the config. Path/URL generation is derived from the disk, so changing a disk later means backfilling rows, not just editing config.
  4. First use case — deduplicated upload. Accept an upload and add it to a collection (e.g. $post->addMediaFromRequest('image')->toMediaCollection('images')). Upload two byte-identical files and you should see one original on disk and two rows pointing at it. That single behaviour is the whole value proposition of the package; verify it on day one before building anything else on top.
  5. Then try replace(). Single-file collections (avatars, hero images, logos) are the second thing you'll need, and as of 1.1.4 replace() is transaction-safe, which makes it safe to call inside the same transaction as the model update.

Implementation Patterns

Deduplicating adds and replaces. Use the normal add path for user uploads — identical bytes collapse onto one original automatically. For collections that permit exactly one item, prefer replace() over delete-then-add: it keeps the media row (and therefore its ID and, for in-place overwrites, its URL) stable.

Sharing an existing original. attach() (same model, other collection) and same-disk copy() (other model) point a new row at an existing original rather than re-uploading. Use these for "set as thumbnail", "duplicate this product's gallery", or seeding fixtures — they're effectively free. Since 1.1.4 they take a lock on the row they share with, so they will block behind a concurrent forceDelete() rather than resurrect a deleted file.

Transaction boundaries. The package's rule of thumb is now: files follow the commit.

  • forceDelete() inside DB::transaction() deletes files only on commit; a rollback leaves files intact and the row restored.
  • replace() inside a transaction keeps the old bytes aside and puts them back on rollback; an in-place replace can therefore be rolled back cleanly.
  • Wrap the "update model + swap media" pair in one transaction and you no longer need manual file-orphan cleanup on failure.

Keep media transactions short. All three fixes work by holding row locks. A transaction that adds/replaces media and then does something slow (HTTP call, PDF render, queued-variant wait) will now block concurrent deletes and competing replaces on the same original. Do heavy work before opening the transaction, or dispatch it after commit.

Deletion policy. Prefer soft deletes for anything user-reversible. Reserve forceDelete() for retention jobs and hard-delete cascades, and make sure those jobs don't run inside a longer transaction that might abort — or accept that the files are only removed on commit (which is the desired behaviour).

Cleanup and reconciliation. Keep an occasional job that scans for originals with no referencing row and for rows with no file. The dedupe design means a single missing original breaks several models at once, so an integrity check is cheaper than debugging individual reports.

Variants and regeneration. Variants hang off the original's checksum. After an in-place replace() the URL stays the same, which is convenient but means regenerating variants is your responsibility — schedule or dispatch it explicitly for any replace path.

Gotchas and Tips

  • ModelNotFoundException from attach() / copy() is new and expected. If the shared row is gone by the time the lock is acquired, these throw instead of silently creating a dangling reference. Catch it and fall back to a real upload if the source is user-initiated; if it happens in a job, re-queue.
  • Deduplicating add/replace degrades gracefully, sharing methods don't. When the target original disappears, an add or replace stores its own copy; attach()/copy() fail loudly. That asymmetry is deliberate — code that must never fail should use the add path.
  • In-place replace keeps its URL. Great for CDN-free setups, terrible for aggressively cached ones. If you front media with a CDN, purge by URL after a replace, or force a new path (which changes the URL and gives you a free cache bust).
  • Deadlock potential. replace() locks its own row then checks other referrers with a locking read; attach()/copy() lock the row they share with. Two transactions touching overlapping media sets in different orders can still deadlock. Keep transactions single-purpose, and be prepared to retry (DB::transaction($cb, 3)).
  • replace() on a deleted row throws ModelNotFoundException. Guard against stale form submissions and queued jobs holding a media ID that has since been force-deleted.
  • Rollback semantics are not the same for both replace strategies. In-place writes restore the old bytes; new-path writes delete the new file. Both leave the row on its old checksum — but only if you actually roll back the surrounding transaction, so don't catch exceptions inside the transaction and continue as if nothing happened.
  • Debugging pointers. When a file "mysteriously disappears", the cause is almost always a connection that couldn't see an uncommitted sharing row. If you're on more than one DB connection or a read replica, remember that the row locks only protect you on the connection doing the write — route media writes to the primary.
  • Testing. Write tests that run two connections/processes against the same file when you touch delete or replace code; a single-connection test suite will never reproduce the 1.1.3 bugs and will happily pass while production corrupts itself. If you swap the media model via config (custom model class, host applications), note that factory support for this only landed in 1.3.1 — earlier versions build the base model regardless, so pin the version your tests assume and assert on get_class() in at least one factory test.
  • Extension points. Hook variant generation and CDN purging into the package's model events rather than overriding controllers — commit-time file deletion means event timing has shifted, so listen for the delete/commit signal rather than assuming files vanish inside the forceDelete() call.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib