Module development
Start by reading existing modules. The service provider is the important part:
notes/src/NotesServiceProvider.phpsignature/src/SignatureServiceProvider.php
notes is a good example for a module with migrations, routes, permissions and resources. signature is a good example for a small module with a custom frontend field.
Build the smallest module that owns the reusable behaviour. Keep customer-specific resources, menus and policies in Skeleton unless the module must ship them for every app.
Every module service provider extends QoreModuleServiceProvider and implements:
public function configure(QoreModuleConfigurator $configurator): void
Minimal Provider
class MyModuleServiceProvider extends QoreModuleServiceProvider
{
public const string MODULE_NAME = 'my-module';
public function configure(QoreModuleConfigurator $configurator): void
{
$configurator
->setName(self::MODULE_NAME)
->useTranslations()
->onRegister(function (QoreModule $module) {
$module
->setTitle(__('my-module::module.title'))
->setDescription(__('my-module::module.description'));
});
}
}
setName() should match the package/module name suffix. If no package name is set, Qore uses qore-next/{name}.
Configurator API
setName(string $name): selfsetPackageName(string $packageName): selfuseTranslations(): selfuseMigrations(): selfuseConfig(): selfuseRoutes(): selfuseChannels(): selfonRegister(Closure(QoreModule): void $callback): selfonEnable(Closure(QoreModule): void $callback): selfonDisable(Closure(QoreModule): void $callback): selfonBoot(Closure(QoreModule): void $callback): selfsetSettingsFields(Closure(): Field[] $callback): selfonSettingsSubmit(Closure(array $validated): void $callback): self
What The Base Provider Does
In register():
- creates a configurator using the module root directory;
- calls your
configure(); - builds a
QoreModule; - merges config when
useConfig()is enabled; - registers a publish tag
qore-next.{module}.config.
In boot():
- loads translations from
langwhenuseTranslations()is enabled; - loads migrations from
database/migrationswhenuseMigrations()is enabled; - registers publish tag
qore-next.{module}.db; - loads
routes.phpwhenuseRoutes()is enabled; - requires
channels.phpwhenuseChannels()is enabled; - runs
onRegister(); - registers the module in
qore()->modules().
Enable, Disable and Boot
onRegister() runs when the provider boots and should set title/description.
onEnable() runs when the module is enabled. Use it for permissions or one-time setup.
->onEnable(function () {
$permissions = qore()->permissions()->cruda('notes');
admin_role()->givePermissions(collect($permissions)->values());
})
onDisable() runs when the module is disabled. Use it for cleanup.
onBoot() runs for enabled modules from ModuleService::bootModules(). Use it to register resources, policies, drivers or runtime behaviour.
->onBoot(function () {
Gate::policy(notes_model_class(), notes_policy_class());
qore()->registerResource(notes_resource_instance());
})
Settings
Use settings fields when a module needs database-backed configuration.
->setSettingsFields(fn () => [
PasswordField::make('api_key')
->setDefaultValue(fn () => qore()->settings()->getSetting('my-module.api_key', '')),
])
->onSettingsSubmit(function (array $validated) {
qore()->settings()->setSetting('my-module.api_key', $validated['api_key'] ?? '');
})
Folder Shape
Use only the folders your module needs:
src: provider, fields, resources, services, policies.resources/js: custom frontend fields/nodes.resources/css: module styling.database/migrations: migrations loaded/published withuseMigrations().config/{module}.php: config loaded/published withuseConfig().lang/en,lang/nl: translations loaded withuseTranslations().routes.php: routes loaded withuseRoutes().channels.php: broadcast channels loaded withuseChannels().
Keep reusable code in the module. Keep application-specific resources in Skeleton.