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

Add field into existing form

Inject a custom field into a core MikoPBX form from your module.

A module often needs to attach extra data to an existing core entity — an extension, a provider, a queue — without forking the Core. MikoPBX exposes two Web-UI hooks that together let a module surgically extend a form it does not own:

  1. onBeforeFormInitialize(Form $form, $entity, $options) — add Phalcon form elements (inputs, selects, checkboxes) to a core form before it is built.

  2. onVoltBlockCompile(string $controller, string $blockName, View $view) — inject a partial Volt template into a named block of a core view, so those new elements actually render on the page.

To persist the value you add a model in your module and relate it to the core model with getDynamicRelations().

Throughout this page we extend the core Extensions edit form with a new field from a fictional module ModuleBlackList (config class BlackListConf, model BlackListNumbers, table m_BlackListNumbers). The real, working reference for this exact pattern is ModuleUsersUI, which uses both hooks to add an access group selector to the same Extensions form — see Extensions/ModuleUsersUI/Lib/UsersUIConf.php.

The two Web-UI hooks live on the module config class (BlackListConf extends ConfigClass). The base implementations are no-ops in Core/src/Modules/Config/ConfigClass.php (namespace MikoPBX\Modules\Config); you only override the ones you need. The third piece — getDynamicRelations() — is not a config-class hook: it is a static method you declare on your own model class (see Step 3). For the full hook catalogue see hooks-reference.md.

How the hooks fire

Both hooks are dispatched by the Core, so you never call them yourself.

onBeforeFormInitialize is invoked from BaseForm::initialize() for every admin form:

Core/src/AdminCabinet/Forms/BaseForm.php
public function initialize($entity = null, $options = null): void
{
    if ($entity === null) {
        $entity = new stdClass();
    }
    PBXConfModulesProvider::hookModulesMethod(
        WebUIConfigInterface::ON_BEFORE_FORM_INITIALIZE,
        [$this, $entity, $options]
    );
}

onVoltBlockCompile is invoked from the Volt hookVoltBlock(...) function while a template compiles. Core views declare named injection points like this:

For each hookVoltBlock call the Core gathers a partial path from every module (Core/src/AdminCabinet/Providers/VoltProvider.php), passing your config class the controller name (Extensions) and the block name (TabularMenu / AdditionalTab). The Extensions edit form exposes both TabularMenu (the tab strip) and AdditionalTab (the tab bodies) — that is the pair we target.

Step 1 — Add the form element

Override onBeforeFormInitialize in your config class. Guard on the exact core form class, then add Phalcon form elements.

Use a unique, prefixed field name (here module_black_list_blocked). It must not collide with any core field name on the form, and the prefix makes it easy to recognise your fields in the submitted POST data later. ModuleUsersUI follows the same convention with a module_users_ui_ prefix.

ModuleUsersUI builds a richer set of elements (Text, Password, Check, Hidden, Select) in a dedicated helper and adds them all to the same form — see the verified reference in Extensions/ModuleUsersUI/App/Forms/ExtensionEditAdditionalForm.php (prepareAdditionalFields()), called from onBeforeFormInitialize in Extensions/ModuleUsersUI/Lib/UsersUIConf.php.

Step 2 — Render the field with a Volt partial

Adding the element to the form object is not enough — nothing renders it until you return a partial template for the relevant block. Override onVoltBlockCompile and match on the "$controller:$blockName" pair.

Now create the two partials. The tab label:

And the tab body that renders the element you added in Step 1. Reference the field by the exact name you registered:

Compare with the working originals: Extensions/ModuleUsersUI/App/Views/Extensions/tabularmenu.volt and .../additionaltab.volt, which render module_users_ui_* fields with form.render(...).

A Semantic-UI toggle checkbox needs $('.ui.checkbox').checkbox() to initialise. Because you are extending a page owned by the core controller, you cannot add the asset from your own indexAction/modifyAction — use the onAfterAssetsPrepared(Manager $assets, Dispatcher $dispatcher) hook (WebUIConfigInterface::ON_AFTER_ASSETS_PREPARED, Core/src/Modules/Config/WebUIConfigInterface.php:49,111), which hands you the asset manager plus the dispatcher so you can check which controller/action is being rendered before attaching anything. Name the file for the action you are extending — module-black-list-extensions-modify.js — since asset names are per-action, not per-module. Asset loading is its own topic and is otherwise out of scope for this page.

Step 3 — Persist the value with a model relation

The field now renders and submits, but you still need somewhere to store it. Create a module model backed by your own table and relate it to the core model with a static getDynamicRelations() method.

When a core model is initialized (ModelsBase::initialize()addExtensionModulesRelations()), the Core scans each enabled module's Models/ directory and, when a model class declares getDynamicRelations(), calls it so the module can attach relationships to the core model being loaded:

Declare the relation from your model toward the core Users model (the entity behind an extension):

Real, verified examples of getDynamicRelations():

  • Extensions/ModuleAutoprovision/Models/ModuleAutoprovisionUsers.php — a live hasMany relation to Users (the pattern reproduced above).

  • Extensions/EXAMPLES/WebInterface/ModuleExampleForm/Models/ModuleExampleForm.php — a documented getDynamicRelations() stub showing a belongsTo to a core Providers model.

Putting it together

Goal
Hook / method
Where it fires

Add the form element

onBeforeFormInitialize(Form $form, $entity, $options)

BaseForm::initialize()

Render the element

onVoltBlockCompile(string $controller, string $blockName, View $view)

Volt hookVoltBlock(...)

Relate to the core model

getDynamicRelations(&$calledModelObject) (static, on your model)

ModelsBase

See also

Last updated

Was this helpful?