Plugin Builder
Follow this workflow to build plugins exactly like existing plugins/webkul/* modules.
Workflow
- Read
references/plugin-patterns.mdfirst. - Inspect the target plugin and its nearest sibling modules (usually
purchases,sales,projects,website,accounts). - Implement/adjust service provider first (
*ServiceProvider) usingPackageServiceProvider+Packagechain. - Implement/adjust plugin class (
*Plugin) for panel-aware registration (admin,customer). - Ensure package composer wiring exists (
composer.jsonPSR-4,extra.laravel.providers, plugin factory/seeder namespaces). - Register ACL surface in
config/filament-shield.php. - Add or align policies under
src/Policiesso generated permissions resolve cleanly. - Add settings migration(s) + settings class + settings page(s) when feature flags/configuration are needed.
- Add dashboard/pages/widgets and clusters/resources based on panel + module scope.
- Add table views to list/manage pages using
HasTableViews+PresetView. - Add/update translations using plugin language folder conventions and nested keys.
- Verify installation and permission lifecycle with install commands.
Required Conventions
- Extend
Webkul\PluginManager\PackageServiceProviderfor each plugin service provider. - Configure package metadata using chained methods on
Package:name,hasViews,hasTranslations,hasRoute/hasRoutes,hasMigrations,runsMigrations,hasSettings,runsSettings,hasSeeder,hasDependencies,hasInstallCommand,hasUninstallCommand,icon. - Add installation dependencies in both places:
->hasDependencies([...])and install command->installDependencies(). - Use plugin install command pattern:
->hasInstallCommand(fn (InstallCommand $command) => $command->installDependencies()->runsMigrations()->runsSeeders())(adjust seeders when plugin has none). - Register plugin into Filament via
Panel::configureUsing(fn (Panel $panel) => $panel->plugin(YourPlugin::make()));. - Gate plugin registration with install state in plugin class:
if (! Package::isPluginInstalled($this->getId())) { return; }. - Ensure plugin package
composer.jsonincludes:autoload.psr-4plugin namespace,extra.laravel.providersservice provider entry, and plugin database factory/seeder namespaces when used. - For admin/front split, use panel conditions in
register(Panel $panel):$panel->when($panel->getId() == 'admin', ...)and/or... == 'customer'. - Discover Filament components from panel-specific folders:
discoverResources,discoverPages,discoverClusters,discoverWidgets. - Register ACL manage/exclude entries in
config/filament-shield.phpfor resources/pages. - Keep
RoleResource/shield flow compatible with plugin key naming fromPluginManager\PermissionManager. - Use settings with Spatie Laravel Settings:
settings migration in
database/settings/*, class insrc/Settings/*, settings UI asSettingsPagewhen needed. - Use table views on list/manage pages with:
use HasTableViews;and implementgetPresetTableViews(): arrayreturningPresetViewentries. - Use plugin translation structure under
resources/lang/enand keep keys nested by UI surface (navigation,form.sections,table.columns,table.filters,infolist.sections,actions,notifications).
Admin + Front Panel Setup
Mirror purchases, website, and blogs when plugin needs both panels:
- Structure Filament folders by panel:
src/Filament/Admin/...andsrc/Filament/Customer/.... - In plugin
register(): - Configure customer panel resources/pages/clusters/widgets under the
customercondition. - Configure admin panel resources/pages/clusters/widgets under the
admincondition. - Add panel-specific navigation items (for example settings shortcuts) with visibility checks.
- If customer auth UX is custom (example:
website), configure panel auth pages in plugin registration. - For front/customer experience, also align plugin
routes/web.php, frontend views, and Livewire components with panel behavior when module exposes customer-facing pages.
Translation Convention
- Keep plugin translation root at
plugins/<vendor>/<plugin>/resources/lang/en. - Mirror Filament location in translation path:
filament/resources/...,filament/admin/...,filament/customer/...,filament/clusters/...,filament/pages/...,filament/widgets/..., plusmodels,enums, and top-levelapp.phpwhere needed. - Keep resource/page translation file layout explicit. Example:
resources/lang/en/filament/resources/product.php(resource-level labels)resources/lang/en/filament/resources/product/pages/create-product.phpresources/lang/en/filament/resources/product/pages/edit-product.phpresources/lang/en/filament/resources/product/pages/list-products.phpresources/lang/en/filament/resources/product/pages/view-product.php- For admin/customer split, prefix under panel path:
resources/lang/en/filament/admin/...resources/lang/en/filament/customer/...- Keep file names kebab-case and aligned with resource/page/action names.
- Prefer deeply nested, predictable arrays grouped by UI surface:
navigation.title,navigation.groupform.sections.<section>.titleform.sections.<section>.fieldsets.<fieldset>.titleform.sections.<section>.fields.<field>.label|helper-texttable.columns,table.groups,table.filterstable.record-actions.<action>.notifications.<state>.title|bodytable.toolbar-actions.<action>.notifications.<state>.title|bodytable.empty-state.heading|descriptioninfolist.sections.<section>.entries.<entry>.label|helper-text- Use consistent key spelling (
filters, notfileters). - Keep translation keys stable and consume them from code via
__('plugin::path.to.key'). __()usage examples:- Resource label:
__('products::filament/resources/product.navigation.title') - Page title:
__('products::filament/resources/product/pages/create-product.title') - Field label:
__('products::filament/resources/product.form.sections.general.fields.name')
Extending Base Resources and Models
Prefer extension over duplication when a base module already defines the domain behavior.
- Extend base Filament resources from source plugins (common in
accounting,invoices,sales,purchases,contacts,recruitments). - Keep child resource focused on deltas: cluster assignment, pages map, minor schema/table overrides, extra actions.
- For models, follow plugin-local model namespace and existing trait conventions (
HasFactory, soft deletes, sortable, permission traits where applicable).
ACL, Roles, and Permission Lifecycle
- Define plugin resource/page permission scope in plugin
config/filament-shield.php. - Keep cluster/page excludes updated so only intended permission keys are generated.
- Ensure policy methods match expected permission keys.
- Rely on install lifecycle to regenerate/sync permissions (plugin install command and ERP install flow).
- When changing ACL surface, validate role permission assignment via security role management UI/logic.
Settings, Dashboard, and Table Views
- Settings:
- Add settings migration in
database/settings. - Add settings class extending
Spatie\LaravelSettings\Settings. - Add settings page extending
Filament\Pages\SettingsPage(commonly clustered under settings). - Dashboard/pages/widgets:
- Use page shield traits on custom dashboards/pages.
- Register widget sets via dashboard page
getWidgets()/getHeaderWidgets(). - Place pages/widgets in panel-appropriate folders.
- Table views:
- Add
HasTableViewsto list/manage pages. - Define named preset tabs with
PresetView::make(...)->modifyQueryUsing(...). - Use favorites/default presets where relevant.
Verification Checklist
- Plugin appears in plugin manager and installs via
<plugin>:install --no-interaction. - Dependencies auto-install in correct order.
- Admin and/or customer panel components appear only on intended panel.
- Shield permissions include new resources/pages and exclude intended clusters/pages.
- Policies resolve and UI actions obey authorization.
- Settings persist and settings pages load.
- Dashboard pages/widgets render without permission regressions.
- Table preset views apply expected query behavior.