Extension Basics

How WemX discovers, loads and manages extensions, plus the folder structure and properties every extension shares.

An extension is a folder under extensions/ with a main class that extends one of the WemX base classes. WemX discovers it, registers it in the extensions table, and loads its views, routes, migrations, translations, config, commands and scheduled tasks only while it is enabled.

Extension types

TypeFolderMain classBase class
Serverextensions/Servers/{Name}/Server.php → Extensions\Servers\{Name}\ServerApp\Extensions\Foundation\ServerExtension
Gateway (one-time)extensions/Gateways/{Name}/Gateway.php → Extensions\Gateways\{Name}\GatewayApp\Extensions\Foundation\GatewayExtension
Gateway (subscription)extensions/Gateways/{Name}/Gateway.phpApp\Extensions\Foundation\SubscriptionGatewayExtension
Moduleextensions/Modules/{Name}/Module.php → Extensions\Modules\{Name}\ModuleApp\Extensions\Foundation\ModuleExtension

The class name is fixed: it's the singular of the parent folder (Servers → Server). The namespace mirrors the path, because Extensions\ is PSR-4 autoloaded from extensions/.

Folder structure

Only the main class is required. Everything else is picked up automatically if it exists:

extensions/Modules/Example/
├── Module.php            ← main class (required)
├── routes.php            ← loaded with loadRoutesFrom()
├── Views/                ← view namespace = extension id, e.g. view('example::page')
│                           Volt components in here are mounted automatically
├── Lang/                 ← translation namespace = extension id, e.g. __('example::messages.key')
├── Migrations/           ← loaded with loadMigrationsFrom()
├── Config/config.php     ← merged as config('example.*')
├── Models/, Http/, Actions/, Commands/ …  ← your own classes, autoloaded by namespace
└── LICENSE, README.md

You can change the default locations with the $views, $translations, $migrations, $routes and $config properties on the main class.

Common properties

Every extension class declares metadata as protected properties:

protected string $id = 'example';               // unique identifier (slugified), also the view/lang/config namespace
protected string $name = 'Example';             // display name
protected string $description = 'What it does.';
protected string $icon = 'puzzle';              // icon name or image path
protected string $type = 'Module';              // Server | Gateway | Module
protected string $version = '1.0.0';
protected array  $wemxVersions = ['*'];         // compatible WemX versions, '*' for all
protected array  $authors = [
    ['name' => 'Your Name', 'email' => 'you@example.com'],
];
protected string $marketplace_id;               // optional: ID of the resource on the marketplace

Optional hooks (all extension types)

Implement any of these methods on the main class, and WemX calls them if they exist:

MethodWhen it's called
providers(): arrayAt boot (when enabled). Return service provider classes to register.
commands(): arrayAt boot. Return Artisan command classes.
schedule(Schedule $schedule): voidWhen the scheduler resolves. Add scheduled tasks.
elements(): arrayWhen the extension is enabled. Register menu items and widgets. See UI elements.
onEnable() / onDisable()When an admin enables or disables the extension.
setSettingsFields(): arrayAdds a settings page under Settings → Third Party (see below).
use Illuminate\Console\Scheduling\Schedule;

public function commands(): array
{
    return [Commands\SyncCommand::class];
}

public function schedule(Schedule $schedule): void
{
    $schedule->command('example:sync')->hourly()->withoutOverlapping();
}

Extension settings page

Return a settings array from setSettingsFields(). Values are stored in the global settings table under the keys you choose, so read them with settings('your_key'):

public function setSettingsFields(): array
{
    return [
        'title' => 'Example Settings',
        'settings' => [
            'example_api_url' => [
                'label' => 'API URL',
                'description' => 'Base URL of the Example API.',
                'type' => 'text',
                'rules' => 'required|url',
            ],
            'example_mode' => [
                'label' => 'Mode',
                'type' => 'select',
                'options' => ['live' => 'Live', 'test' => 'Test'],
                'rules' => 'required|in:live,test',
            ],
        ],
    ];
}

Prefix your setting keys with your extension ID so they don't collide with core settings or other extensions. Include encrypted in a key (for example example_encrypted_token) to store it encrypted.

Lifecycle

 files placed in extensions/…
          │
          ▼  php artisan extension:discover   (or "Reload" in the admin)
 row created in `extensions` table  (status: disabled)
          │
          ▼  enable  (admin UI or php artisan extension:enable)
 onEnable() runs → elements() registered → status: enabled
          │
          ▼  every request while enabled
 providers, views, routes, lang, migrations, config, commands, schedule loaded
          │
          ▼  disable
 onDisable() runs → elements removed → status: disabled (data kept)

Migrations are registered while an extension is enabled, so php artisan migrate (or php artisan extension:migrate {name}) runs them. Modules usually also call $this->migrate('extensions/Modules/Example/Migrations') in onEnable(), so enabling from the admin panel sets up the database straight away.

Scaffolding

php artisan make:module Example     # extensions/Modules/Example/Module.php
php artisan make:server Example     # extensions/Servers/Example/Server.php

php artisan extensions:add-routes Example
php artisan extensions:add-views Example
php artisan extensions:add-migrations Example
php artisan extensions:add-translations Example
php artisan extensions:add-config Example

Generated stubs can lag behind the base classes. After scaffolding, compare your method signatures with the abstract methods in app/Extensions/Foundation/ and the examples in these docs, for example create(Order $order, ServerConnection $connection) for servers.

See the CLI reference for every extension command.

On this page