jd-dotlogics/laravel-media
jd-dotlogics/laravel-media is a Laravel package for attaching, storing, and managing media files with clean model associations, upload handling, media collections, customizable storage disks, and simple APIs for serving images and files in common application workflows.
Verify before you commit. With 0 stars and 0 dependents, this is effectively an unreleased/internal package. Spend ten minutes reading the source before writing any application code — composer require jd-dotlogics/laravel-media, then open vendor/jd-dotlogics/laravel-media/src/, config/media.php (if present), and the migrations directory. The README is the only docs you'll get, and it may be thin or stale.
Minimal install sequence (confirm each step applies once you've read the service provider):
composer require jd-dotlogics/laravel-media
php artisan vendor:publish --provider="JdDotLogics\Media\MediaServiceProvider"
php artisan migrate
php artisan storage:link
Where to look first, in order:
src/MediaServiceProvider.php — what's registered (config, migrations, routes, commands, Blade components).config/media.php — the storage disk, path prefix, allowed mimes, and any conversion queue settings. This is the file you'll touch most in production.HasMedia) — that's the public API surface your models will use.media migration — column names and whether there's a polymorphic mediable_type/mediable_id pair, a collection column, disk, size, mime, and ordering column.First use case to get working end-to-end: attach a single uploaded image to a Post model, render its URL in a Blade view, and confirm the row lands in the media table with the right disk and path. Don't touch conversions or multiple collections until that round-trip works — it validates config, disk, storage link, trait wiring, and URL generation in one pass.
// app/Models/Post.php
use JdDotLogics\Media\Concerns\HasMedia; // confirm exact namespace
class Post extends Model
{
use HasMedia;
}
$post = Post::create($validated);
$post->addMediaFromRequest('cover')->toMediaCollection('cover'); // confirm method names
If those method names don't match the trait, stop and adapt — the rest of this document assumes a Spatie-style API, which is the Laravel convention this package most likely mirrors.
Controller-driven uploads with Form Requests. Keep validation in a FormRequest (image|mimes:jpg,png,webp|max:5120) and do the attach in the controller after the model is persisted. Attaching before save() means no mediable_id to hang the row off.
public function store(StorePostRequest $request)
{
$post = Post::create($request->validated());
if ($request->hasFile('cover')) {
$post->addMediaFromRequest('cover')
->toMediaCollection('cover');
}
return redirect()->route('posts.show', $post);
}
Collections as a contract. Pick a fixed set of collection names (cover, gallery, documents) and treat them as constants — an enum or a Post::COLLECTION_GALLERY const. This keeps retrieval, deletion, and any conversion config sane as the app grows.
Multiple uploads. Loop over the request array rather than assuming a batch API exists:
foreach ($request->file('gallery', []) as $file) {
$post->addMedia($file)->toMediaCollection('gallery');
}
Retrieval and URL generation. Wrap retrieval in accessors so views never touch the media relation directly — this is where you isolate the package if you later swap it out:
public function getCoverUrlAttribute(): ?string
{
return $this->getFirstMediaUrl('cover') ?: null;
}
Use a placeholder (a static asset, not a generated image) when it returns null. Null-coalescing into a hardcoded CDN URL is how you get broken-image reports in production.
Queue heavy work. Any conversions, thumbnails, or remote-disk puts should be queued. The main risk with a zero-adoption package is that it hands you synchronous file processing in the request cycle. If config/media.php exposes a queue connection, set it; if not, consider triggering conversions from a queued job or listener on model creation and letting the request return fast.
Cleanup on delete. Register a deleting hook (or a model observer) to remove media rows and files. Confirm whether the package does this automatically; for a package with no dependents, assume it does the bare minimum and verify with a rollback test.
protected static function booted(): void
{
static::deleting(fn (Post $post) => $post->clearMediaCollection('gallery'));
}
API responses. In API Resources, emit URLs plus width/height/mime metadata rather than raw paths:
'cover' => [
'url' => $this->cover_url,
'thumb' => $this->getFirstMediaUrl('cover', 'thumb'),
],
Testing. Use Storage::fake('public') in feature tests and assert the file lands via Storage::disk('public')->assertExists(...). Because the package is unmaintained, your test suite is the regression net — write one test per collection covering attach, retrieve, and delete.
composer.json ("jd-dotlogics/laravel-media": "1.x" or a commit hash), commit the lock file, and treat upgrades as deliberate migrations rather than composer update.addMediaFromRequest, toMediaCollection, getFirstMediaUrl, and the 'thumb' conversion argument are Spatie conventions. This package may use attach, upload, store, or ->to('cover'). Namespace and method mismatches between README and source are common in low-traffic packages — the source wins.config/media.php points at your intended disk (public vs s3) and that the media.disk value matches filesystems.default if you're relying on the default. Uploading to public while serving from s3 (or vice versa) yields 404s that look like permission bugs.storage:link is per-environment. Add it to your deploy script. Local works, staging 404s — classic.deleting hook defensively and run a periodic pruning command (php artisan media:clean if one is registered — check src/Console/).config/media.php, run php artisan config:clear. Forgetting this makes config changes appear to have no effect and sends you debugging the wrong layer.IMG_0001.jpg uploads into the same collection will collide or overwrite. Check whether it hashes/uniqid()s filenames; if not, wrap the attach in a custom useFileName() or your own path generator.image|max:5120 in a FormRequest gives you clear 422s and doesn't depend on unmaintained internals.PathGenerator or filename callback (for CDN-friendly layouts like posts/{id}/cover.webp), a custom Conversions class if the package supports per-model conversion definitions, and the ability to override the published config for allowed mimes and max size.MediaService or trait of your own (attachCover(Post $post, UploadedFile $file)). If the package proves unmaintained mid-project, you replace one class instead of hunting call sites across 40 controllers. Given the adoption numbers, this is not paranoia — it's the primary architectural decision here.laravel-medialibrary (mature, heavily used, same mental model) before building on it. The API surface described above is nearly identical, which makes a future migration cheap if you keep the wrapper layer thin.How can I help you explore Laravel packages today?