Skip to main content
A new ARUKZ ERP module is a folder under 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

IDs instead of names, 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 BelongsToCompany (company separation and an automatic company_id) and take numbers from SequenceService, never count() + 1:
Relate to 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 uses BelongsToCompany.
  • 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 only discoverClusters() scans the cluster folder.
  • Permissions generated and granted to the company Admin role.
  • Filament 5 class names only; tests for isolation, numbering and permissions pass.
Last modified on November 12, 2026