Skip to main content

Creating an Addon

This guide walks you through creating a new addon from scratch using the Artisan command-line tool.

Generate the Addon

Open your terminal, navigate to the Mumara Campaigns root directory, and run:

php artisan module:make YourAddonName

Replace YourAddonName with your addon's name using PascalCase (e.g., EmailValidator, CrmConnector, AdvancedReports).

Create Multiple Addons

You can create multiple addons at once:

php artisan module:make EmailValidator CrmConnector AdvancedReports

Generated Structure

The command creates a new directory under Addons/YourAddonName/ with the following structure:

Addons/YourAddonName/
├── module.json
├── composer.json
├── package.json
├── webpack.mix.js
├── functions.php
├── menu.json
├── Config/
│ └── config.php
├── Console/
├── Database/
│ ├── Migrations/
│ └── Seeders/
│ └── YourAddonNameDatabaseSeeder.php
├── Helpers/
├── hooks/
│ └── hooks.php
├── Http/
│ ├── Controllers/
│ │ └── YourAddonNameController.php
│ ├── Middleware/
│ └── Requests/
├── Models/
├── Providers/
│ ├── YourAddonNameServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
├── public/ ← Static assets (web-accessible)
│ ├── css/
│ ├── js/
│ └── images/
├── Resources/
│ ├── assets/ ← Source assets (private, for compilation)
│ │ ├── js/
│ │ │ └── app.js
│ │ └── sass/
│ │ └── app.scss
│ ├── lang/
│ └── views/
│ └── index.blade.php
├── Routes/
│ ├── web.php
│ └── api.php
├── Settings/
│ ├── config.php
│ ├── install/
│ └── uninstall/
└── Tests/
├── Feature/
└── Unit/

Configure the Addon

After generation, configure your addon's metadata.

1. Edit module.json

The module.json file is your addon's manifest:

{
"name": "YourAddonName",
"alias": "youraddonname",
"description": "Description of what your addon does",
"keywords": ["mumara", "addon", "feature"],
"priority": 0,
"providers": [
"Addons\\YourAddonName\\Providers\\YourAddonNameServiceProvider",
"Addons\\YourAddonName\\Providers\\EventServiceProvider"
],
"files": [],
"requires": []
}
FieldDescription
nameAddon name (PascalCase, must match directory name)
aliasLowercase identifier used in views, config, and routes
descriptionBrief description of the addon
keywordsSearch keywords for discoverability
priorityLoad order (lower loads first)
providersService providers to register
filesAdditional PHP files to autoload (e.g., ["Helpers/helper.php"])
requiresDependencies on other addons

2. Edit Settings/config.php

Configure addon properties displayed in the addon manager:

<?php

$setting = [
'name' => 'Your Addon Name',
'vendor' => 'Your Company',
'type' => 'Integration',
'logo' => 'logo.png',
'version' => '1.0.0',
'route' => 'youraddon',
'install_dir' => 'YourAddonName',
'license_type' => 'free', // native, marketplace, remote, or free
'new_version_url' => '',
'update_url' => '',
'license_key' => ''
];
FieldDescription
nameDisplay name in addon manager
vendorYour company or developer name
typeCategory (Integration, Feature, Utility, etc.)
logoLogo filename in Settings/ directory
versionCurrent version number (semver)
routeBase route prefix for your addon
install_dirDirectory name under Addons/
license_typeLicensing model
new_version_urlURL to check for updates
update_urlURL to download updates
license_keyLicense key (if applicable)

3. Configure composer.json

Set up PHP autoloading and dependencies:

{
"name": "yourcompany/youraddonname",
"description": "Your addon description",
"type": "library",
"require": {},
"autoload": {
"psr-4": {
"Addons\\YourAddonName\\": ""
}
}
}

The main application uses wikimedia/composer-merge-plugin to automatically merge your addon's composer.json with the root dependencies. After adding new dependencies, run:

composer update

Set Up the Service Provider

The main service provider bootstraps your addon. Edit Providers/YourAddonNameServiceProvider.php:

<?php

namespace Addons\YourAddonName\Providers;

use Illuminate\Support\ServiceProvider;

class YourAddonNameServiceProvider extends ServiceProvider
{
protected string $moduleName = 'YourAddonName';
protected string $moduleNameLower = 'youraddonname';

public function boot(): void
{
$this->registerHooks();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
}

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

protected function registerHooks(): void
{
foreach (glob(module_path($this->moduleName, 'hooks/*.php')) as $file) {
require_once $file;
}
}

protected function registerTranslations(): void
{
$langPath = resource_path('lang/modules/' . $this->moduleNameLower);

if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->moduleNameLower);
} else {
$this->loadTranslationsFrom(module_path($this->moduleName, 'Resources/lang'), $this->moduleNameLower);
}
}

protected function registerConfig(): void
{
$this->publishes([
module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower . '.php'),
], 'config');

$this->mergeConfigFrom(
module_path($this->moduleName, 'Config/config.php'),
$this->moduleNameLower
);
}

protected function registerViews(): void
{
$viewPath = resource_path('views/modules/' . $this->moduleNameLower);
$sourcePath = module_path($this->moduleName, 'Resources/views');

$this->publishes([
$sourcePath => $viewPath
], ['views', $this->moduleNameLower . '-views']);

$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower);
}

private function getPublishableViewPaths(): array
{
$paths = [];
foreach (config('view.paths') as $path) {
if (is_dir($path . '/modules/' . $this->moduleNameLower)) {
$paths[] = $path . '/modules/' . $this->moduleNameLower;
}
}
return $paths;
}
}

Create Your First Route

Edit Routes/web.php:

<?php

use Illuminate\Support\Facades\Route;
use Addons\YourAddonName\Http\Controllers\YourAddonNameController;

Route::middleware(['web', 'auth'])->prefix('youraddon')->group(function () {
Route::get('/', [YourAddonNameController::class, 'index'])->name('youraddon.index');
Route::get('/settings', [YourAddonNameController::class, 'settings'])->name('youraddon.settings');
});

Create a Controller

Edit Http/Controllers/YourAddonNameController.php:

<?php

namespace Addons\YourAddonName\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class YourAddonNameController extends Controller
{
public function __construct()
{
$this->middleware(['auth', '2fa']);
}

public function index()
{
return view('youraddonname::index', [
'title' => 'Your Addon'
]);
}

public function settings()
{
return view('youraddonname::settings');
}
}

Create a View

Edit Resources/views/index.blade.php:

@extends('layouts.master2')

@section('title', $title)

@section('content')
<div class="kt-container kt-container--fluid kt-grid__item kt-grid__item--fluid">
<div class="kt-portlet">
<div class="kt-portlet__head">
<div class="kt-portlet__head-label">
<h3 class="kt-portlet__head-title">{{ $title }}</h3>
</div>
</div>
<div class="kt-portlet__body">
<p>Welcome to your addon!</p>
</div>
</div>
</div>
@endsection

Add a Menu Item

Edit menu.json to add sidebar navigation:

[
{
"title": "Your Addon",
"icon": "<i class='kt-menu__link-icon flaticon-app'></i>",
"url": "youraddon",
"hidden": false,
"hasChild": false,
"childrens": []
}
]

For nested menus:

[
{
"title": "Your Addon",
"icon": "<i class='kt-menu__link-icon flaticon-app'></i>",
"url": "#",
"hidden": false,
"hasChild": true,
"childrens": [
{
"title": "Dashboard",
"icon": "<i class='menu-bullet menu-bullet-dot'><span></span></i>",
"url": "youraddon",
"hidden": false
},
{
"title": "Settings",
"icon": "<i class='menu-bullet menu-bullet-dot'><span></span></i>",
"url": "youraddon/settings",
"hidden": false
}
]
}
]

Set Up Public Assets

Create the public/ directory for your addon's static assets:

mkdir -p Addons/YourAddonName/public/css
mkdir -p Addons/YourAddonName/public/js
mkdir -p Addons/YourAddonName/public/images

Place your compiled CSS, JavaScript, images, and fonts here. These become web-accessible once the addon is enabled and the symlink is created (see Views & Assets).

Install the Addon

After setting up your addon:

  1. Log into Mumara Campaigns as admin
  2. Navigate to Setup > Addons
  3. Find your addon in the list
  4. Click Install

The system will:

  • Run database migrations
  • Publish configuration
  • Create the public asset symlink (public/Addons/YourAddonName/ → Addons/YourAddonName/public/)
  • Enable the addon

Verify Installation

After installation:

  1. Clear the cache: php artisan cache:clear
  2. Navigate to your addon's route (e.g., /youraddon)
  3. Verify the menu item appears in the sidebar
  4. Check that static assets load correctly (inspect browser console for 404 errors)

Next Steps