plugins/arukz/ with a composer.json, its migrations, an Eloquent model that uses BelongsToCompany, a service provider that registers the install command, and a Filament plugin class that adds its screens to the panel. Permissions need no code: any resource under the Arukz\ namespace is checked against Shield permissions automatically. The worked example below adds dyeing batches for a textile mill.
First, should it be a plugin?
Every company shares one codebase, so a plugin built for one customer is installed for the whole platform. Make it generic (“Dyeing batches”), not about one customer.
The files
1
Autoload the namespace
Give the plugin a
composer.json with PSR-4 autoloading ("Arukz\\TextileDyeing\\": "src/"). The root composer.json merges every plugins/*/*/composer.json; by house convention also add the namespace to the root autoload.psr-4 next to the other Arukz\\ entries, then run composer dump-autoload.2
Write a company-scoped migration
company_id on the table, numbers unique per company, restrictOnDelete where history must survive, and indexes on what you filter by. See Data model.3
Write the model
Use Relate to
BelongsToCompany (company separation and an automatic company_id) and take numbers from SequenceService, never count() + 1:Webkul\Security\Models\User, not App\Models\User: the Webkul model has the partner, allowed companies and isPlatformOperator().4
Register the service provider
Extend
Webkul\PluginManager\PackageServiceProvider. Its $name becomes the plugin ID, the plugins table row and the textile-dyeing:install command. Declare migrations and dependencies, and add the Filament plugin to the panel:5
Add the Filament plugin and screens
The plugin class is named like the provider with
Plugin in place of ServiceProvider. In register(), return early when the plugin is not installed, then call discoverClusters() for the cluster folder only. Build the resource with Filament 5 class names:Relationship selects (
Select::make('partner_id')->relationship('partner', 'name')) are limited to the current company by the scope automatically.6
Generate and grant permissions
Every action on a model whose resource lives under
Arukz\ needs the matching Shield permission, named <action>_<module>_<resource>, for example view_any_textile_dyeing_dyeing::batch. Generate them with php artisan shield:generate --all --option=permissions --panel=admin (the install command does it too), then grant them to the roles that should use the module. Until then nobody without the permission can open the screens. Write a policy class only for a rule beyond “does the role have it”.7
Register, install and reload
Add the provider to
bootstrap/providers.php, run composer dump-autoload, run php artisan textile-dyeing:install as the web server user, then rebuild the caches and reload PHP-FPM (see Operations).8
Test it
At least: a batch made in company A is not returned to a user of company B; two companies both get number
0001; a user without view_any_... cannot open the list. tests/Feature/Arukz/CompanyScopingTest.php also checks every ARUKZ model uses BelongsToCompany, and ModuleIntegrityTest fails if any ARUKZ file imports a class that does not exist.Checklist before a pull request
- Every business table has
company_id, and the model usesBelongsToCompany. - Foreign keys, not copied names; uniqueness per company.
- Numbers from
SequenceService. hasDependencies(...)lists every plugin whose tables you reference.register()returns early when the plugin is not installed, and onlydiscoverClusters()scans the cluster folder.- Permissions generated and granted to the company
Adminrole. - Filament 5 class names only; tests for isolation, numbering and permissions pass.