Filament DAM — Enterprise Digital Asset Management for Filament
A comprehensive Digital Asset Management plugin for Filament v4 and v5 and Laravel 12, built as a premium layer on top of codenzia/filament-media. It competes with enterprise DAM platforms like Pimcore, Bynder, and MediaINFO while staying 100% native to the Filament ecosystem — no iframes, no service-proxy hacks, no external control planes.
Why this exists. Most "DAM" Filament plugins stop at uploads + folders. Real DAM begins where that ends: renditions per channel, brand portals for stakeholders, rights tracking with expiry alerts, smart collections that keep themselves current, AI enrichment, duplicate hunting, analytics, and first-class distribution. This package delivers all of that behind a handful of pages and services you can lean on from day one.
Try it live: A working integration is included in the Codenzia plugins demo at
/admin/demo/dam.
Table of Contents
- Highlights
- Feature Matrix
- Requirements
- Installation
- Quick Start
- Configuration
- Architecture
- Database Schema
- Modules
- Eloquent Trait —
HasDamAssets - Artisan Commands
- Events
- Enumerations
- API / Routes
- Panel Pages
- Widgets
- Front-end Build (Tailwind v4)
- Filament v4 vs v5 Compatibility
- Extending
- Performance Notes
- Security Considerations
- Testing
- Troubleshooting
- Roadmap
- Contributing
- Changelog
- Credits
- License
Highlights
- Enterprise feature parity — renditions, portals, rights, smart collections, analytics, AI enrichment.
- Filament v4 and v5 native —
^4.0 || ^5.0in one codebase; property types and namespaces follow the v4/v5 conventions documented in Codenzia's engineering standards. - Tailwind v4 CSS-first —
@themeand@sourcedriven stylesheet, notailwind.config.js, compiled via PostCSS + esbuild intoresources/dist/. - SDUI / Schemas-first — all layout built with
Filament\Schemas\Components; Actions extendFilament\Actions\Action. - Feature-flagged — every module toggles via
config/filament-dam.php, so you pay only for what you enable. - Public surfaces — brand portals, share links, embed routes, oEmbed JSON endpoint, and on-demand rendition delivery ship out-of-the-box.
- No raw SQL — Eloquent models, relationships, and scopes throughout, with typed returns.
- Tested with Pest v3 — enum behaviour, model scopes, service logic, focal geometry, smart-collection queries, and duplicate hashing are covered.
Feature Matrix
| Category | Capabilities |
|---|---|
| Renditions | Auto-generate variants (WebP, AVIF, JPEG, PNG) per output profile with configurable dimensions, quality, and transformation pipeline. |
| Focal Points | User-defined focal-point cropping that preserves the area of interest across any target aspect ratio. |
| Brand Portals | Branded, public-facing asset galleries with password protection, expiry dates, per-portal colors, logo, and custom CTA. |
| Share Links | Expirable, password-protectable download links with max-downloads caps and audit logs. |
| Rights Management | License templates (Royalty Free, Rights Managed, Creative Commons, Editorial, Internal, Custom), per-asset rights, territory enforcement, expiry alerts. |
| Smart Collections | Saved-search collections that auto-resolve to matching assets (type, tags, metadata, size, date). |
| AI Enrichment | Tag suggestions, descriptions, alt text via Anthropic Claude or OpenAI GPT-4o. |
| Duplicate Detection | Perceptual aHash (Hamming distance) + byte-for-byte file-hash for exact matches. |
| Color Extraction | k-means dominant palette per image. |
| Metadata Extraction | EXIF + IPTC + GPS → structured JSON column. |
| Usage Tracking | Polymorphic where-is-this-used tracker with orphan detection. |
| Embeds | Tokenised iframe embed + oEmbed JSON endpoint for external distribution. |
| Analytics | Storage, uploads, downloads, top assets, license expiry — surfaced via widgets and a service API. |
| CDN Ready | Point config.cdn.url at your CDN; rendition URLs and delivery service rewrite automatically. |
| Feature Flags | Every module can be disabled individually. |
Requirements
| Dependency | Version | Notes |
|---|---|---|
| PHP | ^8.3 |
Strict typing + promoted constructor properties. |
| Laravel | ^12.0 |
Resolved via orchestra/testbench for tests. |
| Filament | ^4.0 || ^5.0 |
Dual support — same package, both majors. |
codenzia/filament-media |
^0.1 || dev-main |
Upload, folders, tags, versions, search. |
intervention/image |
^3.11 |
Rendition + transformation pipeline. |
spatie/laravel-package-tools |
^1.17 |
Install command, asset publishing. |
ext-gd |
required | Image resize, k-means palette, aHash. |
Optional peers (unlocked when installed):
laravel/scout— full-text search with Meilisearch or Typesense.league/flysystem-aws-s3-v3— S3 storage for renditions and originals.codenzia/filament-workflow— approval workflows for publishing assets.
Installation
Run the install command (interactive — publishes config + migrations):
…or do it manually:
Register the plugin on your Filament panel provider:
There are two registration modes depending on which media-file model your app uses.
Mode 1 — Layered (default, recommended)
Your app uses Codenzia\FilamentMedia\Models\MediaFile. Register both plugins:
Plugin IDs are unique (filament-media / filament-dam) so they coexist in the same panel. To toggle the basic media manager and settings pages, use FilamentMediaPlugin::make()->showMediaManager(false) / ->showSettings(false).
Mode 2 — DAM-as-unified (opt-in)
Your app uses Codenzia\FilamentDam\Models\MediaFile (DAM's own model — has saved searches, store panel, batch import). Do not register FilamentMediaPlugin — opt in to DAM owning the media-manager UI:
Use this mode when you reference Codenzia\FilamentDam\Models\MediaFile directly in your application code. Registering FilamentMediaPlugin alongside in this mode would cause both plugins to fight for the /admin/media route.
Show toggles
Every show* method on FilamentDamPlugin takes a boolean — pass false to hide the corresponding panel page without touching your config. Defaults:
| Toggle | Default | Currently registers | Notes |
|---|---|---|---|
showSettings() |
on | DamSettings page |
DAM Settings (separate from Media Settings) |
showSmartCollections() |
on | DamSmartCollectionResource |
Gated on smart_collections feature |
showSubscriptionPlans() |
on | DamSubscriptionPlanResource + UserSubscriptions page |
Gated on subscriptions feature |
showMediaManager() |
off | Pages\Media (when opted in) |
Mode-2 opt-in |
showMediaSettings() |
off | Pages\MediaSettings (when opted in) |
Mode-2 opt-in |
showDashboard() |
on | (no-op, reserved) | Toggle exists for forward-compat; page not yet wired |
showAssetBrowser() |
on | (no-op, reserved) | Toggle exists for forward-compat; page not yet wired |
showPortals() |
on | (no-op, reserved) | Will gate BrandPortals page once wired |
showLicenses() |
on | (no-op, reserved) | Will gate LicenseManager page once wired |
showProfiles() |
on | (no-op, reserved) | Will gate OutputProfiles page once wired |
The asset-detail page (/admin/view-asset) is always registered when the plugin is loaded — no toggle needed since it has no nav entry and is reached only via direct link. Widgets (ExpiringLicensesWidget, SubscriptionStatsWidget, StorageSpaceChartWidget, TopAssetsWidget, UploadTrendsWidget, SubscriptionsWidget) register automatically when their respective features (rights_management, subscriptions, analytics) are enabled in DAM's feature config.
Quick Start
That's the 30-second tour. Read on for the modules you actually want.
Configuration
All configuration lives in config/filament-dam.php. The headline knobs:
Feature Flags
Toggle any module without touching code:
AI
Tip. To use Claude 4.6 / 4.7, just update
providers.anthropic.model. The provider abstraction insideAutoTaggerServicedoesn't care which specific model you point it at.
Renditions
CDN
Notifications
Route Prefixes
Every public surface's URL prefix and middleware are configurable:
Default Output Profiles (seeded)
Navigation
Architecture
The split is deliberate: teams that only need a media manager stay lean with filament-media; teams that need a full DAM add filament-dam on top without rewriting their upload pipeline.
Database Schema
Fourteen tables ship with this package, plus six columns added to media_files.
| Table | Purpose |
|---|---|
dam_profiles |
Output profile definitions (Thumbnail, Social Media, Website Hero, …). |
dam_renditions |
Generated variants per file + profile (path, size, mime, dimensions). |
dam_focal_points |
User-defined focal points (x, y in 0.0–1.0). |
dam_portals |
Brand portal configurations (name, slug, brand color, logo, password, expiry). |
dam_portal_assets |
Portal ↔ media file pivot with ordering. |
dam_share_links |
Shareable download tokens (password, max downloads, expiry). |
dam_download_logs |
Download tracking / audit (IP, UA, link, file). |
dam_licenses |
License template definitions (type, attribution, territories). |
dam_asset_rights |
Per-asset license assignments (cost, photographer, agency, expiry). |
dam_usage_records |
Polymorphic where-is-this-used tracking. |
dam_smart_collections |
Saved search criteria (pinned, notify-on-match). |
dam_embeds |
Embed / oEmbed configurations per file. |
dam_enrichment_jobs |
AI processing status queue. |
dam_analytics_events |
Generic event tracking. |
Columns added to media_files:
| Column | Type | Purpose |
|---|---|---|
dam_status |
string |
draft / in_review / approved / published / archived / rejected. |
color_palette |
json |
Dominant colors extracted via k-means. |
perceptual_hash |
string |
aHash for duplicate detection. |
ai_description |
text |
AI-generated long description. |
ai_tags |
json |
AI-suggested tags (pre-apply). |
exif_data |
json |
Structured EXIF + IPTC + GPS. |
Modules
Renditions & Output Profiles
Define output profiles (channels) once, generate renditions on demand or eagerly.
Transformation Pipeline
Any profile can chain transformations — each step maps to the TransformationType enum:
Supported transformations: resize, crop, focal_crop, watermark, format_convert, quality, blur, sharpen, grayscale, rotate, flip, brightness, contrast. Each exposes requiredParams() and affectsDimensions() so you can build editor UI against the enum.
On-demand delivery URL
Renders (or serves the cached copy of) the requested rendition, honouring the cdn.url override when set.
Focal Point Cropping
Give editors the ability to declare "this is the important area" — the crop pipeline honours it for every target aspect ratio.
A lightweight Alpine component (damFocalPicker) ships in resources/js/filament-dam.js for click-to-set focal-point UIs — see Front-end Build (Tailwind v4) for how to bundle it.
Brand Portals
Curated, public, branded galleries — the modern "Google Drive link" for brand assets.
Portals respect expires_at, optional password, and per-portal download allowance. All visits can be surfaced through the analytics module.
Share Links
Trackable, expirable, password-protectable file links — purpose-built for client previews, contractor handoff, and one-time review cycles.
Downloads are throttled (throttle:30,1) and logged in dam_download_logs — every byte out of the door is auditable.
Rights & License Management
Treat licensing as a first-class citizen, not a metadata field.
Each LicenseType declares whether it requiresExpiry() or requiresAttribution(), so form validation and UI hints stay in sync with licensing reality.
Smart Collections
Saved-filter collections that always reflect the current library state.
Pinned collections bubble to the top of the panel nav; notify_on_match lets downstream listeners react (e.g. Slack when a new product hero hits the library).
AI Enrichment
Claude or GPT-4o, behind one abstraction.
Batch from the CLI:
Config.ai.auto_enrich_on_upload + enrichment_tasks let you fire metadata / color / hash extraction automatically when files land.
Duplicate Detection
Both exact and perceptual.
duplicates.threshold in config sets the default Hamming distance (5 = visually similar, 0 = identical).
Color Extraction
k-means dominant palette — handy for design systems, product-page color matching, and visual search.
Palettes live on media_files.color_palette (JSON) once extracted.
Metadata Extraction
Full EXIF, IPTC, and GPS — no manual parsing.
Usage Tracking
Polymorphic "where is this used" tracking — indispensable for cleanup and impact analysis.
The HasDamAssets trait wires deleting automatically, so tracked usage is cleared when a host model is deleted.
Embeds & oEmbed
Embeddable iframes + oEmbed JSON for consumption by external editors (Notion, Ghost, Contentful, etc.).
Analytics
Each of these feeds one of the shipped widgets.
Eloquent Trait — HasDamAssets
Drop the HasDamAssets trait on any model that owns assets. It extends HasMediaFiles with DAM-specific helpers:
The trait registers a deleting hook that removes usage records automatically, so no stale "this is used by Product 42" rows after deletes.
Artisan Commands
| Command | Description | Recommended Schedule |
|---|---|---|
dam:generate-renditions |
Generate renditions for media files (by profile / file, or bulk). | Queue on upload or run nightly. |
dam:enrich |
Run AI enrichment + metadata / color / hash extraction. | On-demand or backfill. |
dam:check-expiry |
Notify about expiring licenses, share links, and portals. | Daily. |
dam:cleanup |
Delete expired share links and trim old analytics events. | Weekly. |
Suggested routes/console.php:
Events
| Event | Fired When |
|---|---|
RenditionGenerated |
A new rendition is saved to disk. |
AssetStatusChanged |
Asset status transitions (draft → in_review → approved → published → archived / rejected). |
AssetEnriched |
AI enrichment completes for a file. |
AssetShared |
A share link is created. |
AssetDownloaded |
A share link or direct download fires. |
LicenseExpiring |
A license crosses one of the configured expiry_thresholds. |
Enumerations
Every enum implements the Filament contracts (HasLabel, HasColor, HasIcon where meaningful) so you can reference them from forms, tables, and infolists without hard-coding strings or colors.
| Enum | Cases | Notable Methods |
|---|---|---|
AssetStatus |
Draft, InReview, Approved, Published, Archived, Rejected |
allowedTransitions(), canTransitionTo() |
LicenseType |
RoyaltyFree, RightsManaged, CreativeCommons, Editorial, Internal, Custom |
requiresExpiry(), requiresAttribution() |
TransformationType |
13 cases (Resize, Crop, FocalCrop, Watermark, FormatConvert, Quality, Blur, Sharpen, Grayscale, Rotate, Flip, Brightness, Contrast) |
requiredParams(), affectsDimensions() |
RenditionFormat |
Webp, Avif, Jpeg, Png |
Format metadata (mime, extension). |
API / Routes
Public Routes
| Method | URL | Description |
|---|---|---|
GET |
/dam/portal/{slug} |
View a brand portal. |
POST |
/dam/portal/{slug}/auth |
Submit portal password. |
GET |
/dam/share/{token} |
View a shared file. |
POST |
/dam/share/{token}/auth |
Submit share-link password. |
GET |
/dam/share/{token}/download |
Download shared file (throttled 30/min). |
GET |
/dam/embed/{token} |
Embed iframe view. |
GET |
/dam/embed/{token}/oembed |
oEmbed JSON endpoint. |
GET |
/dam/rendition/{fileId}/{profileSlug} |
On-demand rendition (throttled 60/min). |
All prefixes (dam/portal, dam/share, dam/embed) and middleware stacks are configurable in config/filament-dam.php.
Panel Pages
| Page | Purpose | Feature Flag |
|---|---|---|
DamDashboard |
At-a-glance tiles linking to every module. | — (core) |
AssetBrowser |
Filterable grid of every asset (search, status filter, type filter, tag filter). | — (core) |
BrandPortals |
CRUD + share UI for brand portals. | portals |
LicenseManager |
Licenses, per-asset rights, expiry monitoring. | rights_management |
SmartCollections |
Criteria builder + live match counts. | smart_collections |
OutputProfiles |
Profile CRUD + transformation pipeline editor. | renditions |
DamSettings |
Feature flags, AI, CDN — all editable via the panel. | — (core) |
Each page is a standalone Filament\Pages\Page subclass with a Livewire-driven schema, so you can extend or replace any of them in your app-level panel.
Widgets
| Widget | Type | Description | Feature Flag |
|---|---|---|---|
UploadTrendsWidget |
Line Chart | 30-day upload trend. | analytics |
TopAssetsWidget |
Table | Most downloaded assets. | analytics |
ExpiringLicensesWidget |
Stats Overview | Expired and expiring license counts. | rights_management |
Widgets self-register via the plugin when the relevant feature flag is on.
Front-end Build (Tailwind v4)
This package uses Tailwind v4's CSS-first configuration — no tailwind.config.js anywhere.
Build commands
Under the hood:
build:styles→npx tailwindcss -i resources/css/filament-dam.css -o resources/dist/filament-dam.css --postcss --minifybuild:scripts→node bin/build.js(esbuild bundle, IIFE, ES2020, tree-shaken, minified)- PostCSS →
@tailwindcss/postcssplugin (seepostcss.config.cjs)
How assets are wired
When resources/dist/filament-dam.css (or .js) exists, FilamentDamServiceProvider::registerFilamentAssets() registers them with Filament's asset system — they'll be injected into every panel that loads this plugin. If the files don't exist yet (e.g. fresh install without a build), the provider silently skips registration — everything else still works.
Theme tokens
The stylesheet exposes a small set of tokens under @theme:
Override them in your app's theme.css to re-skin DAM surfaces without forking the package.
Filament v4 vs v5 Compatibility
This package ships with one codebase that satisfies Filament v4 and v5. Key compatibility rules (applied everywhere):
- Property types follow the v4/v5 union form —
string|\BackedEnum|null $navigationIcon,string|\UnitEnum|null $navigationGroup. - Pages use non-static
protected string $view(v4/v5 broke static). - Layout / Sections / RichEditor live under
Filament\Schemas\Components\*, Actions underFilament\Actions\Action. - Tables use
->recordActions(…)instead of the removed->actions(…), and->schema(…)instead of->form(…). - No
bulkActions()/toolbarActions()/BulkActionGroup/DeleteBulkAction— those classes no longer exist in v5. Placeholder::make()replaced withTextEntry::make()->state().- Form fields hide labels via
->hiddenLabel()(NOT on filters/summarizers — they keep->label('')). callable $setinafterStateUpdated(…)replaced withFilament\Schemas\Components\Utilities\Set $set.- The
unique()validation rule includesignoreRecord: trueby default in v5 — we don't re-specify it. Filament\Support\Icons\Heroiconenum is used for icons, not raw strings.
See Codenzia's engineering standards (CLAUDE.md) for the full v3 → v4/v5 migration delta.
Extending
Custom AI Provider
Custom Transformation
Extend TransformationPipeline and register your subclass as a singleton — every RenditionService::generate() call goes through it.
Custom Notification Recipients
Custom Rendition Storage
Swap the rendition disk in config/filament-dam.php (renditions.disk / renditions.path) — point it at S3 for CDN-backed delivery and set cdn.url to rewrite public URLs.
Performance Notes
- Renditions are lazy by default. Flip
renditions.eagertotruewhen upload latency is less important than first-view latency. - Rendition URLs are cached. Once generated, the file path is resolved from
dam_renditionswithout re-running the pipeline. - Analytics queries use simple aggregates against indexed columns — no N+1 joins, safe to call from dashboard widgets on every page load.
- Smart collections resolve against the
media_filestable with eager-loaded tags. Large libraries benefit from MySQL FULLTEXT / Meilisearch vialaravel/scout. - Duplicate detection stores
perceptual_hashon each file; library-wide scans are O(n²) on the hash column only — for >100k files, runscanAll(limit: …)in batches. - CDN rewrites happen in
DistributionService— no runtime branching at the blade layer, so template caching stays effective.
Security Considerations
- Share tokens are 40-char random strings. Treat them as bearer credentials — don't paste them in ticket systems without scoping.
- Portal & share passwords are hashed with
bcryptviaIlluminate\Support\Facades\Hash. - Download endpoint is rate-limited to 30/min per IP (
throttle:30,1). - On-demand rendition endpoint is rate-limited to 60/min per IP.
- Public routes mount under
webmiddleware by default — CSRF-protected for POST, session-backed for password entry. - No
env()in runtime code. API keys live inconfig/filament-dam.phpand are referenced viaconfig('filament-dam.ai.providers.…'). - Authorization hooks. Panel actions use
->authorize('ability')— wire them to your Gate / Policy layer per module (see each page class).
Testing
The suite uses Pest v3 and currently covers:
- All enum methods, transitions, and derived metadata.
- Model relationships, casts, and scopes (portals, share links, rights, smart collections, embeds, analytics events).
- Service business logic (rendition generation, share expiry, rights checks, focal-point math, smart-collection resolution, k-means color extraction, aHash computation).
- Route registration and middleware wiring.
- Filament page smoke tests (
Livewire::test(PageClass)).
When adding a model or service, generate a smoke test beside it — the tests/Unit/Models/ and tests/Unit/Services/ directories show the expected shape.
Troubleshooting
"Class Filament\Actions\Action not found" — you're on Filament v3. This package targets v4/v5; upgrade or pin to an older tag.
Renditions return 404 — run php artisan storage:link and verify renditions.disk / renditions.path both exist on that disk.
AI enrichment returns empty arrays — ai_enrichment feature flag is off by default. Enable it in config/filament-dam.php and set an API key under ai.providers.<provider>.api_key.
EXIF / IPTC comes back empty — ext-gd is required; ext-exif is recommended. Confirm both with php -m.
Perceptual-hash mismatches — remember the threshold is Hamming distance on the aHash bitstring. 0 = identical, ≤ 5 = visually similar, > 10 = probably unrelated. Tune duplicates.threshold for your library.
Filament v5 panel doesn't show DAM nav — make sure your panel's navigation()->registerNavigationGroups() doesn't override the DAM group that this plugin registers. The plugin adds pages via $panel->pages(); groups are inferred from navigation.group.
Compiled CSS / JS not loading in admin — run npm install && npm run build once. The service provider only registers Css::make() / Js::make() when the dist files exist.
Roadmap
- Approval-workflow integration with
codenzia/filament-workflow(draft → review → publish gates). - Per-portal analytics (visit counts, asset heatmaps).
- Variant A/B testing for renditions.
- Vector-embedding search (bring-your-own-provider).
- Bulk rights assignment + CSV import.
- Livewire-native focal-point editor page (the Alpine helper is already shipped).
Vote / request items via GitHub Issues.
Contributing
Contributions are welcome — issues, ideas, and PRs.
Local setup:
Before opening a PR:
Follow the project-level CLAUDE.md engineering standards — strict typing, promoted constructor properties, Filament Schemas / Actions, Tailwind v4, no raw SQL, no env() outside config.
Changelog
See CHANGELOG.md for the release history.
Credits
- Codenzia — package authoring and maintenance.
- Filament PHP — the admin framework this package extends.
- Intervention Image — image manipulation powering the rendition pipeline.
- Spatie Package Tools — discovery + install scaffolding.
License
Dual licensed under the MIT License (for OSI-approved open-source projects) and a commercial license (for proprietary / SaaS / closed-source use).
See LICENSE.md for the full terms and commercial-licensing contact details.
Built with pride by Codenzia for the Filament ecosystem.