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 Module Laravel Package

idavoll/media-module

A Laravel media module for handling uploads and file storage. It lets you organize media, attach files to Eloquent models, and serve images through configurable disks and conversions, giving your app a reusable media management layer.

View on GitHub
Deep Wiki
Context7

Getting Started

This is an AsgardCMS module, not a standalone package. It provides the media library (upload, folders, thumbnails, media picker) that plugs into an existing AsgardCMS install. Do not try to use it in a plain Laravel app.

Minimum steps:

  1. Confirm compatibility first. Open composer.json in the repo and match its require block (asmcms/core, Laravel version, intervention/image) against your AsgardCMS major version. With one star and no visible maintainers, version drift is the real install risk — pin the core version in your own composer.json before adding this.
  2. composer require idavoll/media-module
  3. Enable the module. AsgardCMS discovers Modules\Media\Providers\MediaServiceProvider; make sure the module is listed in config/modules.php (or modules_statuses.json) and enabled from /admin/modules.
  4. Migrate: php artisan module:migrate Media (plain php artisan migrate works if the module's migrations are auto-loaded). You should get media__files and media__folders style tables.
  5. Publish config and assets: php artisan module:publish-config Media and php artisan module:publish Media. Verify config/media.php and the published JS/CSS exist — the admin picker silently renders nothing without them.
  6. Smoke test: go to /admin/media, upload an image, create a folder, move the file. If that works, the package is wired correctly.

Where to look first, in order:

  • config/media.php — disk, allowed extensions/mimetypes, max size, and the thumbnails array. This is 80% of day-to-day configuration.
  • src/Repositories/FileRepository.php and its contract — the primary API surface for querying and creating files.
  • src/Facades/Imagy.php / the Imagy service — how URLs and thumbnails are resolved.
  • src/Http/Controllers/ — the upload endpoint the admin JS posts to, and the routes/middleware protecting it.

First realistic use case: attach an existing media file to one of your own Eloquent models and render a thumbnail in a Blade view. That exercises storage, the repository, and Imagy in one pass.

Implementation Patterns

Render an existing file anywhere. The File entity exposes path and filename; always resolve display URLs through Imagy rather than building paths by hand, so thumbnails and the configured disk stay consistent:

use Modules\Media\Entities\File;

$file = File::find($id);
$small = Imagy::getThumbnail($file->path, 'smallThumb');
$all   = Imagy::getThumbnails($file->path); // ['smallThumb' => '...', ...]
<img src="{{ Imagy::getThumbnail($file->path, 'smallThumb') }}" alt="{{ $file->filename }}">

Wire the media picker into your own admin form. The module ships a Blade partial used across AsgardCMS admin screens. Include it alongside your normal input and let it write the file ID into a hidden field:

@include('media::admin.fields.file-picker', [
    'name'  => 'image',
    'value' => old('image', $model->image_id ?? null),
])

The exact partial path and the required script include vary by version — copy them from the module's own resources/views/admin form views rather than guessing. Make sure the module's compiled JS is included on that page via the AsgardCMS asset stack.

Persist the relationship yourself. The module doesn't know about your models. Store a file_id column (or a morphTo if you want files attached polymorphically) and add a small accessor:

public function image()  { return $this->belongsTo(File::class, 'image_id'); }
public function getImageUrlAttribute() { return $this->image ? Imagy::getThumbnail($this->image->path, 'mediumThumb') : null; }

Add custom thumbnail sizes. Don't resize at render time — declare sizes in config and let Imagy cache them:

// config/media.php
'thumbnails' => [
    'smallThumb'  => ['width' => 160, 'height' => 160, 'mode' => 'fit'],
    'mediumThumb' => ['width' => 480, 'height' => 480, 'mode' => 'fit'],
    'heroShot'    => ['width' => 1400, 'height' => 600, 'mode' => 'crop'],
],

Hook into uploads with events. For anything async (search indexing, S3 replication, EXIF extraction), listen rather than modify the module. The module fires file lifecycle events around upload and move:

Event::listen(\Modules\Media\Events\FileWasUploaded::class, function ($event) {
    ProcessUploadedFile::dispatch($event->file); // keep handlers small and idempotent
});

Extend the repository through the container. If you need a new query scope, bind your own implementation instead of editing vendor code:

$this->app->bind(
    \Modules\Media\Repositories\FileRepository::class,
    \App\Media\AppFileRepository::class
);

Keep the custom class extending the module's repository and only override what you need, so module upgrades stay mergeable.

Programmatic uploads. For imports or seeders, use the repository (or the upload controller's underlying service) rather than writing rows directly — it handles sanitization, disk storage, and event dispatch. Post to the upload endpoint with the session/CSRF middleware if you're driving it from your own JS.

Gotchas and Tips

  • Namespace confusion. The repo is idavoll/media-module, but classes almost certainly live under Modules\Media\... (AsgardCMS convention), autoloaded from /Modules/Media, not from vendor/. IDE autocomplete often misses it — add the path to your PHPStan/IDE config.
  • Thumbnails are generated lazily. The first request for a new size is slow and does real image work inline. After changing thumbnail config or deploying to a fresh server, warm the cache with a queued job (or a small artisan command) that loops File::cursor() and calls Imagy::getThumbnails($file->path). This alone prevents most "site feels slow after deploy" tickets.
  • Stale thumbnails after replacing a file. The cache key is filename + size, so overwriting a file in place keeps the old thumbnail. Either delete the thumbnail directory (often public/media/thumbnails or the configured thumbnails_path) or version filenames and append a cache-buster to the URL.
  • Driver and extensions. Imagy delegates to intervention/image; the driver is configured in config/image.php (gd vs imagick), not in media.php. If you installed only GD, crop/fit on large images can fail on memory — raise memory_limit or switch drivers.
  • max_size units. Check whether the config value is kilobytes or bytes for your version; the validation message and the actual limit can disagree, which produces confusing "file too large" errors on small uploads.
  • Filename collisions. Uploads are slugified and de-duplicated in the repository. Two files named photo.jpg will get suffixed names depending on version — never assume the stored filename matches the original, and always read $file->filename, not the name you posted.
  • Disk and URL mismatch. Files are written to the disk in media.php, but URLs are built through the filesystem config. A missing storage:link, or a private disk with no url key, yields valid DB rows and broken images with no error. Verify php artisan storage:link and that the disk's url is set.
  • Permissions. Both the upload directory and the thumbnail directory need to be writable by the web user. Upload errors are frequently swallowed and returned as a generic JSON failure — check storage/logs/laravel.log and the disk's permissions before suspecting the code.
  • Orphans and soft deletes. Deleting a File row does not reliably remove the physical file or the generated thumbnails, and deleting your own model leaves the file_id pointing at a live file. Schedule a periodic reconciliation job: compare DB rows to disk contents (and vice versa) and clean up.
  • Upload endpoint is web-middleware protected (session + CSRF). External API consumers or mobile clients need a route you own, wrapping the same service — don't disable CSRF on the module's route.
  • Don't edit the module. Use container bindings, events, and config overrides. If a fix is genuinely needed and upstream is dead (likely, given the single star), fork it into your own packages/ directory with a VCS repository entry in composer.json, so composer update doesn't overwrite your patch.
  • Pin everything. Because this is effectively unmaintained, commit composer.lock and pin the AsgardCMS core tag. An unpinned composer update that bumps core can silently break the module's views, routes, or migration ordering, and you'll have no upstream to ask.
  • Debugging tip: when the picker doesn't appear in a form, check in this order — module enabled, asset stack includes the module's JS, the page actually renders the picker partial and its hidden input. Nine times out of ten it's the missing script include, not the backend.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console