Skip to content

Getting Started - Custom Module Development

Minimum Version Requirement

This feature requires WhatsMarkSaaS v2.0.0 or higher. Ensure your installation meets this requirement before proceeding.

Prerequisites

You must have working knowledge of the following technologies before proceeding:

  • Laravel 12 - Service providers, routing, migrations, Eloquent ORM
  • Livewire 3 - Component lifecycle, properties, actions
  • Tailwind CSS v3 - Utility-first CSS framework
  • Alpine.js v3 - Frontend interactivity
  • Multi-tenant architecture - Tenant-aware data operations and isolation patterns

Overview

Custom modules extend WhatsMarkSaaS functionality without modifying core code. Each module is a self-contained package that can include:

  • Routes (web and API endpoints)
  • Controllers and middleware
  • Eloquent models with tenant scoping
  • Blade templates and Livewire components
  • Translations with multi-tenant support
  • Configuration files
  • Database migrations and seeders
  • Event hooks (WordPress-style actions and filters)
  • Frontend assets (JavaScript, CSS compiled via Vite)

Modules reside in the Modules/ directory at the project root and can be activated, deactivated, and removed independently.


Step 1: Create a Custom Module

Execute the following Artisan command to generate module scaffolding:

bash
php artisan module:make OrderTracker

This command generates a complete module structure at Modules/OrderTracker/.

Command options:

OptionDescription
--forceOverwrites existing module files
(none)Creates new module (fails if exists)

Step 2: Review Generated Structure

The scaffolding command generates the following directory structure:

Modules/OrderTracker/
 Config/
    config.php
 Console/
 Database/
    Migrations/
    Seeders/
    Factories/
 Http/
    Controllers/
       OrderTrackerController.php
    Middleware/
 Livewire/
 Models/
 Providers/
    OrderTrackerServiceProvider.php
    RouteServiceProvider.php
 resources/
    assets/
       js/
       css/
    lang/
       en.json
       tenant_en.json
    views/
        index.blade.php
 Routes/
    web.php
    api.php
 OrderTracker.php
 module.json
 composer.json
 package.json
 vite.config.js
 README.md

Step 3: Understand the Service Provider

The generated OrderTrackerServiceProvider.php handles module registration:

boot() Method

The boot() method registers module resources:

php
public function boot(): void
{
    // Register module translations
    $this->loadTranslationsFrom(module_path($this->moduleName, 'resources/lang'), 'ordertracker');

    // Publish module configuration
    $this->publishes([
        module_path($this->moduleName, 'Config/config.php') => config_path('ordertracker.php'),
    ], 'ordertracker-config');

    // Register module views
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'ordertracker');

    // Load module migrations
    $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
}

register() Method

The register() method registers the route service provider:

php
public function register(): void
{
    $this->app->register(RouteServiceProvider::class);
}

Registering Livewire Components

Add Livewire component registration in the boot() method:

php
use Livewire\Livewire;

public function boot(): void
{
    // ... existing registrations ...

    // Register Livewire components
    Livewire::component('order-tracker::dashboard', \Modules\OrderTracker\Livewire\Dashboard::class);
}

Step 4: Configure the Main Module Class

The OrderTracker.php class extends Corbital\ModuleManager\Support\Module and provides lifecycle methods:

php
<?php

namespace Modules\OrderTracker;

use Corbital\ModuleManager\Support\Module;

class OrderTracker extends Module
{
    /**
     * Register WordPress-style action and filter hooks.
     */
    public function registerHooks(): void
    {
        // Register hooks here
    }

    /**
     * Execute operations during module activation.
     * Runs before migrations and seeders.
     */
    public function activate(): void
    {
        // Setup tasks
    }

    /**
     * Execute operations during module deactivation.
     */
    public function deactivate(): void
    {
        // Cleanup tasks
    }

    /**
     * Execute operations after activation completes.
     * Runs after migrations and seeders.
     */
    public function activated(): void
    {
        // Post-activation tasks
    }

    /**
     * Execute operations after deactivation completes.
     */
    public function deactivated(): void
    {
        // Post-deactivation tasks
    }
}

Lifecycle Method Execution Order

Activation sequence:

  1. activate() - Pre-activation setup
  2. Run migrations (if auto_migrations enabled)
  3. Run seeders (if auto_migrations.seed enabled)
  4. Register routes, views, translations
  5. activated() - Post-activation tasks

Deactivation sequence:

  1. deactivate() - Pre-deactivation cleanup
  2. Unregister routes
  3. deactivated() - Post-deactivation tasks

Step 5: Activate the Module

Execute the activation command:

bash
php artisan module:activate OrderTracker

What happens during activation:

  1. Migrations run automatically (if auto_migrations enabled in config)
  2. Seeders execute after migrations complete
  3. Routes become available and registered with Laravel
  4. Views and translations are registered
  5. The activated() lifecycle method is called

Configuration for auto-migrations:

Check config/modules.php:

php
'auto_migrations' => [
    'enabled' => true,
    'seed' => true,
],

Step 6: Access the Module

After activation, the module routes are accessible via browser:

https://your-domain.com/order-tracker

The route prefix is automatically generated as kebab-case from the module name.

Route examples:

Module NameRoute Prefix
OrderTracker/order-tracker
ProductCatalog/product-catalog
CustomerSupport/customer-support

Step 7: Deactivate (When Needed)

To disable the module without deleting files:

bash
php artisan module:deactivate OrderTracker

What happens during deactivation:

  1. Routes are unregistered
  2. The deactivate() lifecycle method is called
  3. Module files remain on disk
  4. Database tables and data are preserved

Understanding module.json

The module.json file defines module metadata and dependencies:

json
{
    "name": "OrderTracker",
    "alias": "order-tracker",
    "namespace": "Modules\\OrderTracker\\",
    "provider": "Modules\\OrderTracker\\Providers\\OrderTrackerServiceProvider",
    "author": "Module Author",
    "url": "",
    "version": "1.0.0",
    "description": "The OrderTracker Module",
    "keywords": [],
    "order": 0,
    "providers": [
        "Modules\\OrderTracker\\Providers\\OrderTrackerServiceProvider"
    ],
    "aliases": [],
    "require": [],
    "requires_at": "2.0.0",
    "conflicts": [],
    "type": "custom"
}

Field Reference

FieldTypeDescription
namestringModule name in PascalCase
aliasstringURL-friendly identifier (kebab-case)
namespacestringPSR-4 namespace root
providerstringMain service provider class (FQCN)
versionstringSemantic version (e.g., 1.0.0)
orderintegerLoad order (lower = earlier, default: 0)
requirearrayModule dependencies (other module names)
requires_atstringMinimum WhatsMarkSaaS version required
conflictsarrayModules that conflict with this module
typestringAlways custom for developer modules

View and Translation Loading

View Namespace Convention

Views are loaded with lowercase module name as namespace:

php
return view('ordertracker::index', ['data' => $data]);

Loading priority (first match wins):

  1. resources/views/modules/ordertracker/ - Override location (theme customization)
  2. Modules/OrderTracker/resources/views/ - Default module views

Translation Files

Standard translations - resources/lang/en.json:

json
{
    "Order Tracker": "Order Tracker",
    "View Orders": "View Orders"
}

Tenant-specific translations - resources/lang/tenant_en.json:

json
{
    "welcome_message": "Welcome to Order Tracker"
}

Usage in views:

blade
{{ t('Order Tracker') }}

Loading priority (first match wins):

  1. resources/lang/modules/ordertracker/ - Override location
  2. Modules/OrderTracker/resources/lang/ - Default module translations

Next Steps

You have successfully created and activated a custom module. Proceed with:

  1. Module Structure Reference - Understand file organization and conventions
  2. Routing & Controllers - Define web and API routes
  3. Views & Translations - Create Blade templates and multi-language support
  4. Hooks & Events - Integrate with WhatsMarkSaaS hook system
  5. Helper Functions - Use module utilities and helpers

Troubleshooting

Module routes not showing

  1. Verify activation status: php artisan module:activate ModuleName
  2. Clear route cache: php artisan route:clear
  3. Check route list: php artisan route:list --path=module-name

Views not found

  1. Verify view files exist in Modules/ModuleName/resources/views/
  2. Use correct namespace: modulename::viewfile (lowercase, no hyphens)
  3. Clear view cache: php artisan view:clear

Translations not loading

  1. Check JSON files exist in Modules/ModuleName/resources/lang/
  2. Ensure valid JSON format (no trailing commas)
  3. Clear translation cache: php artisan cache:clear

Migration errors on activation

  1. Check migration files for syntax errors
  2. Verify tenant_id column is included where needed
  3. Run manually: php artisan migrate --path=Modules/ModuleName/Database/Migrations

Module not autoloading

  1. Verify composer.json has correct namespace: Modules\\ModuleName\\
  2. Run: composer dump-autoload
  3. Check module.json has correct provider path

This guide is for WhatsMarkSaaS v2.0.0+ custom module development.

© 2024 - Corbital Technologies. All rights reserved.