timo-de-winter/filament-modifiable-plugins
A Filament package that lets you modify installed plugins from your admin panel, providing a simple way to manage plugin settings and behaviour without editing code. Integrates with Laravel and Filament for centralized plugin configuration.
Install the package with Composer and publish its assets before touching any panel configuration:
composer require timo-de-winter/filament-modifiable-plugins
php artisan vendor:publish --tag="filament-modifiable-plugins-config"
php artisan vendor:publish --tag="filament-modifiable-plugins-migrations"
php artisan migrate
Where to look first, in this order:
PanelProvider — the package is registered as a Filament plugin, so the ->plugin(...) call is your integration point.First use case: stop hardcoding ->plugin(SomePlugin::make()) per panel. Register the modifiable-plugins plugin on the panel once, then move each optional plugin into the package's registry so an admin can toggle it and edit its settings from the UI without a deploy. A good pilot is a low-risk plugin (e.g. a widget or a secondary resource) rather than something the panel's core navigation depends on.
Panel-scoped state. Treat every stored setting as (plugin, panel, tenant?). Wrap reads in a single accessor/helper rather than scattering queries through providers — you want one place to add caching later.
Typed reads, not raw arrays. Cast stored settings into a DTO or a readonly value object per plugin. Filament form state is array; leaking that array into business logic makes later schema changes painful.
Cache the resolved registry. Resolving enabled plugins on every request means a query per panel boot. Cache the resolved list keyed by panel (and tenant) and flush explicitly on save, on deploy, and in config:cache flows.
Idempotent seeding across environments. Ship a seeder using updateOrCreate keyed on the plugin identifier + panel so a fresh install matches production defaults. Without it, a new environment boots with nothing enabled and looks broken.
Multi-panel and multi-tenant scoping. If you run several panels, decide deliberately what "no row" means — inherited default, enabled, or disabled. Make that decision explicit in the resolver, not implicit in a first() call.
Custom UI when needed. If the shipped settings page does not fit, build a Filament Resource or custom Page over the underlying model and reuse the package's save path so cache invalidation and events still fire.
Testing. Use RefreshDatabase, disable the package cache in the test environment, and assert on the resolver (what plugins a panel ends up with) rather than on rendered HTML. Factory states for enabled/disabled/configured cover most cases.
Schema::hasTable) or provide a config-level fallback default. Otherwise php artisan migrate itself can fail while resolving panels.vendor:publish --force will clobber your config. Keep published config in version control and diff it after every upgrade instead of blindly republishing. The migration file is likewise yours to own once published.dd() inside the resolver or a temporary listener on the panel booted event) — this distinguishes "not saved" from "saved but not read".How can I help you explore Laravel packages today?