For the complete documentation index, see llms.txt. This page is also available as Markdown.

Recipes

Capability building blocks: the recipes that compose a MikoPBX module.

A MikoPBX module is rarely a single thing. A real module is a combination of capabilities: a settings page, a background worker, a few dialplan hooks, maybe a REST resource. The recipe system is the vocabulary we use to talk about those capabilities one at a time.

A recipe is a self-contained capability. Each recipe answers three questions:

  1. What files does it add to the module tree?

  2. Which hooks or classes does it use from the Core?

  3. Where is the canonical working example in the repository?

This vocabulary is shared by two consumers:

  • Hand-coders — you pick the recipes your module needs and copy the patterns from the linked examples.

  • The /mikopbx-module AI skill — it generates modules by selecting and composing the same recipes. See what the skill generates.

Everything below is anchored to the running example of this guide — a fictional module called ModuleBlackList (config class BlackListConf, main class BlackListMain, model BlackListNumbers backed by table m_BlackListNumbers, front-end script module-black-list-index.js) — and to real, shipping example modules under Extensions/EXAMPLES/.

Recipes are a documentation and generation convention, not a runtime framework. There is no Recipe class. When you "add the workers recipe", you are simply adding the files and the getModuleWorkers() hook that the recipe describes.

The recipe catalog

Recipe
Adds
Integration surface

base

module.json, Setup/PbxExtensionSetup.php, ≥1 Model, Lib/{Feature}Conf.php, Messages/ru.php

Always present — the skeleton every module shares

ui

Controllers, Form, Volt views, JS, CSS

Phalcon MVC + onBeforeHeaderMenuShow()

rest-api

Lib/RestAPI/{Resource}/… (attribute-routed)

Auto-discovered v3 controllers — no manual routes

dialplan

Methods on Lib/{Feature}Conf.php

AsteriskConfigInterface hook methods

agi

agi-bin/{script}.php

MikoPBX\Core\Asterisk\AGI + a dialplan hook

workers

bin/Worker*.php

getModuleWorkers()

firewall

Methods on the Conf class

getDefaultFirewallRules(), onAfterIptablesReload(), generateFail2BanJails()

acl

Methods on the Conf class

onAfterACLPrepared(), applyACLFiltersToCDRQuery(), onGetControllerPermissions(), authenticateUser()

system

Methods on the Conf class

createCronTasks(), createNginxLocations(), onAfterPbxStarted(), …

Every method named above is a real hook. Their full signatures and timing are documented in the hooks reference. This page shows how each recipe uses them; the reference page is the exhaustive list.


Recipe: base (always included)

Every module — including ModuleBlackList — ships the base recipe. Without it there is nothing to install.

Files added:

Integration surface: the heart of the base recipe is the config class. It extends MikoPBX\Modules\Config\ConfigClass, which is abstract and itself extends AsteriskConfigClass. That single inheritance is what makes every hook in every other recipe available to your module — dialplan, workers, firewall, ACL and system hooks are all inherited from this one base class.

See the working example in Extensions/EXAMPLES/WebInterface/ModuleExampleForm/ — specifically:

  • Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Setup/PbxExtensionSetup.php

  • Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Lib/ExampleFormConf.php

  • Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Models/ModuleExampleForm.php

  • Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Messages/ru.php


Recipe: ui (web interface)

The ui recipe gives ModuleBlackList a settings page inside the MikoPBX admin cabinet.

Files added:

Asset filenames are per controller action, not per module: the index action loads module-black-list-index.*, a modify action would load module-black-list-modify.*. This is the convention the Core example follows (module-example-form-index.js, module-example-form-modify.js), and the controller below hardcodes those names, so they must match exactly.

How assets are registered (important)

Modules do not ship a standalone AssetProvider.php file. The Core already provides MikoPBX\AdminCabinet\Providers\AssetProvider, and you reference its collection constants from inside your controller action. This is the verified pattern from the shipping example:

A module controller extends the Core class MikoPBX\AdminCabinet\Controllers\BaseController (there is no separate "module controller" base class). AssetProvider::HEADER_CSS and AssetProvider::FOOTER_JS are the verified collection names; $moduleUniqueID is a property you declare on the controller (set to your module's uniqid) so cached assets are namespaced per module.

How the menu item is added

The sidebar entry is not a MenuProvider.php file either — it is the onBeforeHeaderMenuShow() hook on your config class. The Core calls it while building the admin menu and passes the menu array by reference:

The caption values are translation keys resolved from Messages/ru.php (and en.php).

Post-generation step: JS under public/assets/js/src/ is ES6+ source and must be transpiled with babel into public/assets/js/. The /mikopbx-module skill runs this for you; by hand, use the babel pipeline.

See the working example in Extensions/EXAMPLES/WebInterface/ModuleExampleForm/:

  • Controller — App/Controllers/ModuleExampleFormController.php (the indexAction() / modifyAction() asset pattern shown above is copied verbatim from here)

  • Form — App/Forms/ModuleExampleFormForm.php

  • View — App/Views/ModuleExampleForm/index.volt

  • JS source — public/assets/js/src/module-example-form-index.js

  • Menu hook — Lib/ExampleFormConf.php (onBeforeHeaderMenuShow())


Recipe: rest-api (REST API v3)

This recipe exposes ModuleBlackList data over HTTP. The 2025 v3 pattern is attribute-routed and auto-discovered — you write PHP 8 attributes on a controller, and ControllerDiscovery registers the routes. You never register a route manually, and there is no Conf hook for this.

Files added per resource:

The controller declares routing entirely through attributes; its method bodies are intentionally empty ({}) because the Processor and Actions do the work:

Key facts, all verified against the shipping v3 example:

  • #[ApiResource] marks the class auto-discoverable; ControllerDiscovery scans Lib/RestAPI for *Controller.php and RouterProvider registers the routes.

  • #[HttpMapping] maps HTTP verbs to method names and splits them into resource-level (/numbers/{id}) vs collection-level (/numbers) operations.

  • #[ResourceSecurity] declares auth requirements with SecurityType enum values — SecurityType::LOCALHOST, SecurityType::BEARER_TOKEN.

  • BaseRestController is the base class; $processorClass points at your Processor.

For the full attribute set, the 7-phase Action lifecycle, and DataStructure / OpenApiSchemaProvider, see REST API in modules.

See the working example in Extensions/EXAMPLES/REST-API/ModuleExampleRestAPIv3/:

  • Lib/RestAPI/Tasks/Controller.php

  • Lib/RestAPI/Tasks/Processor.php

  • Lib/RestAPI/Tasks/DataStructure.php

  • Lib/RestAPI/Tasks/Actions/SaveRecordAction.php


Recipe: dialplan (Asterisk dialplan hooks)

The dialplan recipe adds no new files — it adds methods to Lib/BlackListConf.php. Because ConfigClass implements AsteriskConfigInterface, every method below is a real, overridable hook. Implement only the ones you need.

Hook
When it fires
Returns

extensionGenContexts()

Build custom dialplan contexts

string (context text)

extensionGenInternal()

Add rules to [internal]

string

getIncludeInternal()

#include a custom context into [internal]

string

generateIncomingRoutBeforeDial(string $rout_number)

Modify an incoming route before Dial()

string

generateIncomingRoutAfterDialContext(string $uniqId)

Modify an incoming route after Dial()

string

generateOutRoutContext(array $rout)

Modify an outgoing route after EXTEN is set

string

generateOutRoutAfterDialContext(array $rout)

Modify an outgoing route after Dial()

string

extensionGlobals()

Add [globals] variables

string

extensionGenHints()

Add BLF hints

string

getFeatureMap()

Add star-codes to featuremap

string

For ModuleBlackList, the natural hook is generateIncomingRoutBeforeDial() — intercept the inbound call before it rings and route blacklisted callers into an AGI check:

See working examples in real shipping modules:

  • Extensions/ModulePhoneBook/Lib/PhoneBookConf.php — AGI injection on incoming calls

  • Extensions/ModuleAutoDialer/Lib/AutoDialerConf.php — multi-context dialplan generation

The verified hook signatures live in Core/src/Core/Asterisk/Configs/AsteriskConfigInterface.php.


Recipe: agi (AGI scripts)

The agi recipe pairs with the dialplan recipe: a dialplan hook calls AGI(script.php), and that script reads/writes channel variables and runs dialplan applications.

Files added:

Correct AGI API

The method signatures, verified in Core/src/Core/Asterisk/AGI.php:

  • get_variable(string $variable, bool $getvalue = false): array|string — pass true to get the trimmed value directly.

  • set_variable(string $variable, string $value): array

  • exec(string $application, mixed $options): array

Channel request data is also available as $agi->request['agi_callerid'] (and similar agi_* keys), as used by ModuleAutoDialer.

See working examples in real shipping modules:

  • Extensions/ModuleQualityAssessment/agi-bin/quality_agi.php

  • Extensions/ModuleAutoDialer/agi-bin/get-client-info.php

  • Extensions/ModulePhoneBook/agi-bin/agi_phone_book.php


Recipe: workers (background processes)

The workers recipe gives ModuleBlackList a long-running background process — for example, periodically syncing the blacklist from an external feed.

Files added:

(The shipping example keeps its worker classes under Lib/; getModuleWorkers() references the worker by its fully-qualified class name, so any autoloadable location works.)

Integration surface — the getModuleWorkers() hook:

WorkerSafeScriptsCore supervises and restarts your worker. The verified type constants are:

Constant
Value
Use case

WorkerSafeScriptsCore::CHECK_BY_BEANSTALK

checkWorkerBeanstalk

Beanstalk queue / event processing

WorkerSafeScriptsCore::CHECK_BY_AMI

checkWorkerAMI

Real-time AMI call-event tracking

WorkerSafeScriptsCore::CHECK_BY_REDIS

checkWorkerRedis

Workers that report liveness through Redis

WorkerSafeScriptsCore::CHECK_BY_PID_NOT_ALERT

checkPidNotAlert

Long-running daemons (PID monitored)

A module can register multiple workers — the shipping ExampleFormConf::getModuleWorkers() returns both a Beanstalk worker and an AMI worker.

For the worker base class, the start($argv) entry point, Beanstalk subscription and ping/restart loop, see workers.

See working examples in:

  • Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Lib/WorkerExampleFormMain.php

  • Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Lib/WorkerExampleFormAMI.php

  • Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Lib/ExampleFormConf.php (the getModuleWorkers() definition)


Recipe: firewall (firewall + fail2ban)

Adds methods to the Conf class to open ports and define fail2ban jails when the module is enabled.

getDefaultFirewallRules(), onAfterIptablesReload() and generateFail2BanJails() are all real hooks on the config class. Full timing in the hooks reference.


Recipe: acl (access control)

Adds methods to the Conf class that integrate with the MikoPBX role/permission system. All four are verified hooks:

For ModuleBlackList you would use onGetControllerPermissions() to gate the settings page behind a custom permission.

See working examples in Extensions/ModuleUsersUI/Lib/UsersUIConf.php and Extensions/ModuleUsersGroups/Lib/UsersGroupsConf.php.


Recipe: system (system integration)

Adds methods to the Conf class for cron, nginx and lifecycle integration. All verified hooks:

A typical ModuleBlackList use is a nightly cleanup via createCronTasks():

modelsEventChangeData() is how a module reacts to core configuration changes. The shipping example restarts its services when the PBX language changes — see Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Lib/ExampleFormConf.php.


The recipe combination matrix

Modules are built by combining recipes. The base recipe is always present; everything else is additive. Common shapes:

Module type
Recipes
Example

Simple settings page

base + ui

A page with a few options

REST service (no UI)

base + rest-api

Headless integration endpoint

Call processing

base + ui + dialplan + agi

Caller lookup / routing (ModuleBlackList)

CRM integration

base + ui + rest-api + workers + dialplan

Bi-directional sync + screen-pop

Security module

base + ui + firewall + dialplan

Fraud / blocklist with port control

Full-featured module

base + ui + rest-api + dialplan + agi + workers + system

Everything

Worked example: ModuleBlackList

ModuleBlackList is base + ui + dialplan + agi + system:

  • basemodule.json, BlackListConf, BlackListNumbers model (m_BlackListNumbers), Messages/ru.php, Setup class.

  • ui — a settings page to manage blacklisted numbers, menu item via onBeforeHeaderMenuShow(), assets via the controller, module-black-list-index.js.

  • dialplangenerateIncomingRoutBeforeDial() injects an AGI call on inbound calls.

  • agiagi-bin/module-black-list.php checks the caller against BlackListNumbers using new AGI() and snake_case methods.

  • systemcreateCronTasks() runs nightly cleanup.

Each recipe contributed its files and its hooks independently; together they form one coherent module.


Where to go next

Last updated

Was this helpful?