Modules

Build full features as modules, with their own routes, Livewire pages, models, migrations, permissions, commands and scheduled tasks.

A module is the most flexible extension type. Use one to add a whole feature to WemX: support tickets, a downloads area, a knowledge base, an affiliate program. The official Tickets module in extensions/Modules/Tickets is a complete reference implementation, and worth reading alongside this page.

Scaffold

php artisan make:module Downloads
php artisan extensions:add-routes Downloads
php artisan extensions:add-views Downloads
php artisan extensions:add-migrations Downloads
php artisan extensions:add-translations Downloads
php artisan extension:discover

The module class

extensions/Modules/Downloads/Module.php
<?php

namespace Extensions\Modules\Downloads;

use App\Extensions\Foundation\ModuleExtension;
use Illuminate\Console\Scheduling\Schedule;

class Module extends ModuleExtension
{
    protected string $id = 'downloads';            // view namespace: downloads::
    protected string $name = 'Downloads';
    protected string $description = 'Share files with customers.';
    protected string $type = 'Module';
    protected string $icon = 'download';
    protected string $version = '1.0.0';
    protected array $wemxVersions = ['*'];
    protected array $authors = [['name' => 'You', 'email' => 'you@example.com']];

    public function providers(): array
    {
        return [];
    }

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

    public function schedule(Schedule $schedule): void
    {
        $schedule->command('downloads:cleanup')->daily();
    }

    public function elements(): array
    {
        return [
            ['element' => 'navigation-item', 'attributes' => ['name' => 'Downloads', 'href' => '/downloads', 'active' => 'downloads']],
            ['element' => 'admin-sidebar-item', 'attributes' => ['name' => 'Downloads', 'href' => '/admin/downloads', 'icon' => 'download', 'active' => 'downloads']],
        ];
    }

    public function onInstall(): void   { $this->migrate('extensions/Modules/Downloads/Migrations'); }
    public function onUninstall(): void { /* keep customer data */ }
    public function onEnable(): void    { $this->migrate('extensions/Modules/Downloads/Migrations'); }
    public function onDisable(): void   { }
}

ModuleExtension requires the four lifecycle methods: onInstall, onUninstall, onEnable and onDisable. Running migrations in onEnable() means the module works as soon as an admin enables it.

Routes

routes.php is loaded as-is, so apply middleware yourself. Mirror the core's groups:

extensions/Modules/Downloads/routes.php
<?php

use App\Http\Middleware\RequireAdminReauthentication;
use Extensions\Modules\Downloads\Http\Controllers\Admin;
use Extensions\Modules\Downloads\Http\Controllers\Client;
use Illuminate\Support\Facades\Route;

// Client area
Route::middleware(['web', 'auth'])->group(function () {
    Route::get('/downloads', [Client\DownloadsController::class, 'index'])->name('downloads.index');
});

// Admin area: staff only, re-authenticated, permission-checked
Route::middleware(['web', 'auth', 'admin', RequireAdminReauthentication::class])
    ->prefix('admin')->name('admin.')
    ->group(function () {
        Route::get('/downloads', [Admin\DownloadsController::class, 'index'])
            ->name('downloads.index')
            ->middleware('permission:admin.downloads');
    });

Views and Livewire

Views are registered under your extension ID, and Volt components inside Views/ are mounted automatically. Lay out your views per area and theme:

Views/
├── client_area/default/downloads/index.blade.php
└── admin_area/default/downloads/index.blade.php

Render them with client_view() and admin_view() instead of view():

return client_view('downloads::downloads.index');
return admin_view('downloads::downloads.index', ['files' => $files]);

These helpers resolve the view in two steps. First they look for an override in the active theme (resources/client_area/{theme}/downloads/downloads/index.blade.php), then they fall back to your module's client_area/{theme}/… folder. Theme authors can restyle your module without editing it.

In your views, extend the theme layout and use the theme's components so your pages match the rest of WemX:

@extends('theme::layouts.wrapper', ['activePage' => 'downloads'])

@section('title', 'Downloads')

@section('content')
    {{-- x-theme::… components in the client area, x-admin::… in the admin area --}}
@endsection

Permissions

Register permissions in a migration so they appear in the role editor:

DB::table('permissions')->insertOrIgnore([
    ['permission' => 'admin.downloads', 'description' => 'Manage downloads'],
]);

Protect routes with ->middleware('permission:admin.downloads'), and check in code with auth()->user()->hasPermission('admin.downloads').

Models and actions

Put models in Models/ (Extensions\Modules\Downloads\Models\…) and business logic in action classes, like the core does, so validation and side effects live in one place that controllers, Livewire components and commands can all use.

Translations and config

__('downloads::messages.title');   // Lang/en/messages.php
config('downloads.max_size');      // Config/config.php

Hooking into core events

Register listeners from a service provider returned by providers():

use App\Events\Orders\OrderTerminated;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(OrderTerminated::class, function (OrderTerminated $event) {
        // revoke download access for $event->order
    });
}

See Events for the full list.

Uninstall behaviour

Disabling a module removes its elements and stops loading its routes and views, but never deletes data. Follow the same rule in onUninstall() unless the admin explicitly asks for a data wipe.

On this page