Search by

voodflow / vmedia

Voodflow

Filament Media Vault (Filament 5 + Spatie Media Library). Standalone admin, HTTP API, galleries; plugins register their own vault roots.

Package info

github.com/voodflow/vmedia

pkg:composer/voodflow/vmedia

Statistics

Installs: 49

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

0.2.9 2026-09-09 13:45 UTC

This package is auto-updated.

Last update: 2026-09-09 13:45:44 UTC


README

VoodMedia — Filament Media Vault by VoodFlow

Media Vault for Laravel + Filament: upload once, reuse everywhere. One vault storage copy, many gallery memberships, morph attachments for your domain models.

Works standalone in any Filament app. Optional integrations with VoodBuilder (Asset Manager), Voodflow (e.g. Approval Page heroes), and any third-party package that registers its own vault root.

Built on Filament 5 and Spatie Media Library.

Screenshots

Media library

Central library: preview, galleries, usage counts, type, size, and upload time. Upload media or Import ZIP from the header.

VoodMedia media library

Galleries (folders & albums)

Organize vault media with a hierarchy: Folder = container (year, product, plugin…); Album = holds photos/files. Mark a default gallery and toggle public albums.

VoodMedia galleries list

Album editor

Edit album metadata (parent, slug, public/default), manage images (browse library, upload, ZIP import, drag reorder), and assign album tags.

VoodMedia album edit

Picker (VmediaPicker)

Modal for forms and builders: upload into the selected folder/album, filter by parent/gallery, search, grid/list view, multi-select → Use selection.

VoodMedia choose from media library

Requirements

Requirement Notes
PHP 8.4+
Laravel 12 or 13
Filament 5
Spatie Media Library 11
PHP gd extension Must be built with JPEG (and preferably WebP + FreeType). Spatie generates thumb conversions on upload; without JPEG support you get imagecreatefromstring(): No JPEG support in this PHP build.

Verify in the app container:

php -r 'var_export(gd_info()["JPEG Support"] ?? false);'
# expect: true

Docker / php:*-fpm example:

RUN apt-get update && apt-get install -y \
    libpng-dev libjpeg-dev libwebp-dev libfreetype6-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
    && docker-php-ext-install gd

Quick start (5 minutes)

composer require voodflow/vmedia
php artisan vmedia:install

Register on the Filament panel:

->plugins([
    \Voodflow\Vmedia\VmediaPlugin::make(),
])

Attach media to a model:

use Voodflow\Vmedia\Concerns\HasAttachedMedia;

class Booth extends Model
{
    use HasAttachedMedia;
}

Upload in a Filament form:

use Voodflow\Vmedia\Filament\Forms\Components\VmediaFileUpload;

VmediaFileUpload::make('logo')
    ->collection('logo')
    ->image();

Pick existing vault media:

use Voodflow\Vmedia\Filament\Forms\Components\VmediaPicker;

VmediaPicker::make('gallery')
    ->collection('gallery')
    ->images()
    ->multiple();

Plugin vault roots (for packages & host apps)

VoodMedia does not hardcode product folders. Each package (or the host app) registers a top-level folder + default Library album, then uses it for uploads/pickers.

1. Register + ensure on boot

In your package ServiceProvider (or AppServiceProvider):

use Voodflow\Vmedia\Support\Integration\RegistersPluginVault;

public function boot(): void
{
    if (! class_exists(RegistersPluginVault::class)) {
        return;
    }

    // source = stable id (integration_source), slug = folder slug in the UI
    RegistersPluginVault::register(
        source: 'acme',
        slug: 'acme',
        name: 'Acme', // or fn (): string => __('acme::nav.media_root')
        // integrationKey: 'root:acme', // optional; default root:{slug}
    );
    RegistersPluginVault::ensureOnBoot($this->app, 'acme');
}

Equivalent low-level API:

use Voodflow\Vmedia\Vmedia;

Vmedia::registerPluginVault('acme', 'acme', 'Acme');
Vmedia::ensurePluginVault('acme'); // or RegistersPluginVault::ensureOnBoot(...)

After boot you get a folder Acme with a child Library album (upload target when browsing that folder).

Sync all registered roots (after companions have booted):

php artisan vmedia:ensure-plugin-roots

2. Use the vault in code

use Voodflow\Vmedia\Support\Integration\PluginVaultRootGroup;
use Voodflow\Vmedia\Support\Integration\PluginVaultLibraryGallery;
use Voodflow\Vmedia\Support\MediaLibrary;
use Voodflow\Vmedia\Filament\Forms\Components\VmediaPicker;

// Top-level folder (KIND_GROUP)
$root = PluginVaultRootGroup::for('acme');

// Default Library album under that folder (KIND_ALBUM) — prefer this for uploads
$library = PluginVaultLibraryGallery::album('acme');

MediaLibrary::store($uploadedFile, $library);

// Lock a Filament picker to your plugin library
VmediaPicker::make('hero')
    ->images()
    ->attachToRecord(false)
    ->vaultGallery(fn (): int => (int) PluginVaultLibraryGallery::album('acme')->getKey());

Optional: nest under a configured parent (settings / multi-tenant layout):

PluginVaultRootGroup::for('acme', $optionalOuterRootId);

3. Conventions

Concept Rule
source Stable string stored as integration_source (e.g. acme, voodflow)
slug URL/path segment for the folder
Library album Auto-created under the root; use it as default upload target
Isolation Your package owns only its source; do not hardcode other products in VoodMedia

Shared Logos folder is provided by VoodMedia itself (PluginVaultRootGroup::logos()).

Model

  • Vault — singleton Spatie owner of every file on disk (one storage copy)
  • Galleries — many-to-many membership (a photo/video/file can sit in several galleries)
  • Folders vs albums — folders nest structure; albums hold media
  • Default gallery — fallback target for uploads when no gallery is selected; Choose can browse any gallery
  • AttachmentsHasAttachedMedia morph pivot (domain models never own Spatie collections)

Features

Area What you get
Admin Library + galleries hierarchy, metadata (alt, caption, credits; video poster on videos), soft delete / trash
Picker VmediaPicker + VmediaFileUpload for other plugins
Files Photos, videos, and documents (PDF/Office/ZIP…)
Thumbs Spatie thumb conversion (WebP) for images
Usage Attachment counts; delete protected when media is in use
Tags Media tags + album tags; bulk assign
Duplicates SHA-256 reuse (optional)
ZIP import Bulk extract into the vault
Events MediaStored, MediaAttached, MediaDetached, MediaDeleted, MediaRestored
Stats Dashboard widget + php artisan vmedia:stats
Orphans php artisan vmedia:prune-orphans --force
Public GET /galleries/{slug} Blade gallery for is_public galleries

HTTP API (package-owned)

Method Path Role
GET /vmedia/media/galleries Gallery list + counts
GET /vmedia/media?page=&per_page=&gallery_id=&type=&q= Paginated assets (type: image|video|file)
POST /vmedia/media/upload Upload; optional gallery_id (folder/album — folders resolve to library album)
DELETE /vmedia/media/{media} Soft-delete (?force=1 force-deletes)

All routes require authentication (and optional Gate ability VMEDIA_ABILITY).

Config highlights

VMEDIA_ENABLED=true
VMEDIA_DISK=public
VMEDIA_CONVERSIONS=true
VMEDIA_DETECT_DUPLICATES=true
VMEDIA_PROTECT_DELETE=true
VMEDIA_PUBLIC_GALLERIES=true

Storage disk (local / S3 / …)

VMEDIA_DISK is any disk from Laravel’s config/filesystems.php. Spatie stores vault files on that disk (MediaVaultuseDisk(...)).

Local (default):

VMEDIA_DISK=public

S3 (or compatible): define the disk as usual, then point VoodMedia at it:

FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=eu-west-1
AWS_BUCKET=your-bucket
AWS_URL=https://your-bucket.s3.eu-west-1.amazonaws.com
# optional: AWS_ENDPOINT=…  AWS_USE_PATH_STYLE_ENDPOINT=true  (MinIO, R2, …)

VMEDIA_DISK=s3

config/filesystems.php (stock Laravel s3 disk is enough):

's3' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION'),
    'bucket' => env('AWS_BUCKET'),
    'url' => env('AWS_URL'),
    'endpoint' => env('AWS_ENDPOINT'),
    'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
],

Require the Flysystem S3 adapter if the host app does not already:

composer require league/flysystem-aws-s3-v3 "^3.0"

No VoodMedia-specific S3 code path: Filament uploads and Spatie conversions use the configured disk. Keep Livewire temporary uploads on a local disk if you prefer (LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK=local); only the final vault objects need VMEDIA_DISK=s3.

Public URLs come from Storage::disk(...)->url(...) (local → /storage/..., S3 → bucket/CloudFront URL). Use a public bucket (or CDN AWS_URL) so thumbs and embeds resolve without signed URLs.

Docs

License

MIT — see LICENSE.