Minimal Module Examples
Copy-paste gateway, server, registrar, SMS, addon starters
Start here for module development
Copy-paste these templates to scaffold real work. Each example is minimal but functional. Drop into the appropriate modules/ folder, enable in Admin, and extend.
1. Minimal gateway module
Place in modules/gateways/demogateway/. Add module.json and DemogatewayGateway.php.
// module.json
{
"name": "demogateway",
"display_name": "Demo Gateway",
"description": "Minimal gateway example",
"version": "1.0.0",
"author": "Your Company"
}
// DemogatewayGateway.php
<?php
require_once ROOT_PATH . '/core/ModuleInterface.php';
class DemogatewayGateway implements ModuleInterface {
private $config = [];
private $enabled = false;
public function getName(): string { return 'demogateway'; }
public function getType(): string { return 'gateway'; }
public function getDisplayName(): ?string { return 'Demo Gateway'; }
public function getConfigurableOptionKeys(): array { return []; }
public function getConfigurableOptionDefaults(string $optionKey): array { return []; }
public function getConfigFields(): array {
return [
['name' => 'api_key', 'label' => 'API Key', 'type' => 'password', 'required' => true],
['name' => 'test_mode', 'label' => 'Test Mode', 'type' => 'checkbox', 'required' => false]
];
}
public function initialize(array $config): bool {
$this->config = $config;
$this->enabled = !empty($config['api_key']);
return $this->enabled;
}
public function isEnabled(): bool { return $this->enabled; }
public function execute($action, $params = []) {
switch ($action) {
case 'process':
return ['success' => true, 'type' => 'redirect', 'url' => 'https://example.com/pay?invoice=' . ($params['invoice']['id'] ?? 0)];
default:
return ['success' => false, 'error' => 'Unknown action'];
}
}
}
2. Minimal server module
Place in modules/servers/demoserver/. Implements create, suspend, unsuspend, terminate, test_connection, list_packages.
// DemoserverServer.php
<?php
require_once ROOT_PATH . '/core/ModuleInterface.php';
class DemoserverServer implements ModuleInterface {
private $config = [];
private $enabled = false;
public function getName(): string { return 'demoserver'; }
public function getType(): string { return 'server'; }
public function getDisplayName(): ?string { return 'Demo Server'; }
public function getConfigurableOptionKeys(): array { return []; }
public function getConfigurableOptionDefaults(string $optionKey): array { return []; }
public function getConfigFields(): array {
return [
['name' => 'hostname', 'label' => 'Hostname', 'type' => 'text', 'required' => true],
['name' => 'api_key', 'label' => 'API Key', 'type' => 'password', 'required' => true]
];
}
public function initialize(array $config): bool {
$this->config = $config;
$this->enabled = !empty($config['hostname']) && !empty($config['api_key']);
return $this->enabled;
}
public function isEnabled(): bool { return $this->enabled; }
public function execute($action, $params = []) {
switch ($action) {
case 'create':
return ['success' => true, 'username' => $params['username'] ?? 'user', 'domain' => $params['domain'] ?? ''];
case 'suspend':
case 'unsuspend':
return ['success' => true, 'message' => 'Done'];
case 'terminate':
return ['success' => true, 'message' => 'Terminated'];
case 'test_connection':
return ['success' => true, 'message' => 'Connected'];
case 'list_packages':
return ['success' => true, 'packages' => [['id' => 1, 'name' => 'Basic']]];
default:
return ['success' => false, 'error' => 'Unknown action: ' . $action];
}
}
}
3. Minimal registrar module
Place in modules/registrars/demoregistrar/. Implements check_availability, register, renew, get_info, update_nameservers.
// DemoregistrarRegistrar.php
<?php
require_once ROOT_PATH . '/core/ModuleInterface.php';
class DemoregistrarRegistrar implements ModuleInterface {
private $config = [];
private $enabled = false;
public function getName(): string { return 'demoregistrar'; }
public function getType(): string { return 'registrar'; }
public function getDisplayName(): ?string { return 'Demo Registrar'; }
public function getConfigurableOptionKeys(): array { return []; }
public function getConfigurableOptionDefaults(string $optionKey): array { return []; }
public function getConfigFields(): array {
return [
['name' => 'username', 'label' => 'Username', 'type' => 'text', 'required' => true],
['name' => 'api_token', 'label' => 'API Token', 'type' => 'password', 'required' => true]
];
}
public function initialize(array $config): bool {
$this->config = $config;
$this->enabled = !empty($config['username']) && !empty($config['api_token']);
return $this->enabled;
}
public function isEnabled(): bool { return $this->enabled; }
public function execute($action, $params = []) {
switch ($action) {
case 'check_availability':
return ['success' => true, 'available' => true];
case 'register':
return ['success' => true, 'domain' => $params['domain'] ?? ''];
case 'renew':
return ['success' => true, 'expiry' => date('Y-m-d', strtotime('+1 year'))];
case 'get_info':
return ['success' => true, 'expiry' => date('Y-m-d'), 'nameservers' => []];
case 'update_nameservers':
return ['success' => true];
default:
return ['success' => false, 'error' => 'Unknown action: ' . $action];
}
}
}
4. Minimal SMS provider module
Place in modules/sms_providers/demosms/. Implements send action with to and message params.
// module.json
{
"name": "demosms",
"display_name": "Demo SMS",
"description": "Minimal SMS provider example",
"version": "1.0.0",
"author": "Your Company"
}
// DemosmsSmsProvider.php
<?php
require_once ROOT_PATH . '/core/ModuleInterface.php';
class DemosmsSmsProvider implements ModuleInterface {
private $config = [];
private $enabled = false;
public function getName(): string { return 'demosms'; }
public function getType(): string { return 'sms_provider'; }
public function getDisplayName(): ?string { return 'Demo SMS'; }
public function getConfigurableOptionKeys(): array { return []; }
public function getConfigurableOptionDefaults(string $optionKey): array { return []; }
public function getConfigFields(): array {
return [
['name' => 'api_key', 'label' => 'API Key', 'type' => 'password', 'required' => true],
['name' => 'from', 'label' => 'From (Sender)', 'type' => 'text', 'required' => true]
];
}
public function initialize(array $config): bool {
$this->config = $config;
$this->enabled = !empty($config['api_key']) && !empty($config['from']);
return $this->enabled;
}
public function isEnabled(): bool { return $this->enabled; }
public function execute($action, $params = []) {
switch ($action) {
case 'send':
$to = $params['to'] ?? '';
$message = $params['message'] ?? '';
if (empty($to) || empty($message)) {
return ['success' => false, 'message_id' => null, 'error' => 'Missing to or message'];
}
// Call your SMS API here; return success with message_id
return ['success' => true, 'message_id' => 'demo_' . time(), 'error' => null];
default:
return ['success' => false, 'error' => 'Unknown action'];
}
}
}
5. Minimal addon (client area – hooks only)
Place in modules/addons/demoaddon/. Uses hooks to run code in the client area and react to events. No admin page.
// module.json
{
"name": "demoaddon",
"display_name": "Demo Addon",
"description": "Minimal addon – client area hooks",
"version": "1.0.0",
"author": "Your Company"
}
// hooks.php
<?php
require_once ROOT_PATH . '/core/HookManager.php';
HookManager::addAction('slack_invoice_created', function($data) {
$invoiceId = $data['invoice_id'] ?? 0;
$clientId = $data['client_id'] ?? 0;
// Your logic: send to Slack, sync to CRM, etc.
}, 10);
HookManager::addAction('client_footer', function() {
echo '<!-- Demo addon loaded -->';
}, 10);
Enable in Admin → Addons. See Hooks Reference for all available hooks.
6. Minimal addon (admin area – with admin page)
Place in modules/addons/demoadmin/. Adds a config page at /admin/demoadmin. Use admin_url in module.json and create admin/demoadmin.php.
// module.json
{
"name": "demoadmin",
"display_name": "Demo Admin Addon",
"description": "Minimal addon with admin config page",
"version": "1.0.0",
"author": "Your Company",
"admin_url": "/admin/demoadmin"
}
// admin/demoadmin.php (path: modules/addons/demoadmin/admin/demoadmin.php)
<?php
require_once ROOT_PATH . '/includes/bootstrap.php';
$auth = new Auth();
requireStaffAccess('settings');
$db = Database::getInstance();
$module = $db->query("SELECT * FROM modules WHERE name = 'demoadmin' AND type = 'addon'")->fetch_assoc();
$config = $module ? json_decode($module['config_json'] ?? '{}', true) : [];
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save') {
$newConfig = ['api_key' => trim($_POST['api_key'] ?? '')];
$configJson = json_encode($newConfig);
if ($module) {
$stmt = $db->prepare("UPDATE modules SET config_json = ? WHERE name = 'demoadmin' AND type = 'addon'");
} else {
$stmt = $db->prepare("INSERT INTO modules (name, type, folder, enabled, config_json) VALUES ('demoadmin', 'addon', 'demoadmin', 0, ?)");
}
$stmt->bind_param('s', $configJson);
$stmt->execute();
setFlashMessage('success', 'Configuration saved.');
redirect('/admin/demoadmin');
}
$pageTitle = 'Demo Admin Addon';
ob_start();
?>
<div class="card">
<div class="card-header">Demo Admin Addon</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="action" value="save">
<div class="mb-3">
<label class="form-label">API Key</label>
<input type="password" name="api_key" class="form-control" value="<?= e($config['api_key'] ?? '') ?>">
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</div>
<?php
$content = ob_get_clean();
include ROOT_PATH . '/views/layouts/admin.php';
?>
Enable in Admin → Addons. The addon appears in the admin menu; clicking it loads /admin/demoadmin. File path: modules/addons/demoadmin/admin/demoadmin.php.
Config field types
| Type | Use |
|---|---|
text | Single-line input |
password | Masked input (API keys, secrets) |
textarea | Multi-line input |
checkbox | Boolean toggle |
select | Dropdown: add 'options' => ['a' => 'Label A', 'b' => 'Label B'] |
Logging and troubleshooting
Log errors or debug info to PHP error_log: error_log('MyModule: ' . json_encode($params));. Check system_logs, activity_log for provisioning activity. Store module-specific data in services.module_data (JSON) for use in suspend/unsuspend/terminate.
Was this helpful?