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

module.json reference

Field reference for the module.json manifest, version constraints, and how dependencies actually work in MikoPBX.

Every MikoPBX module ships a single module.json file at its root. This manifest is the authoritative source of identity (unique ID, developer, version), the only place where the author declares a PBX compatibility constraint, and the carrier of optional metadata for commercial licensing, language packs, and the release pipeline.

The PBX reads module.json at install time and on every construction of the module's setup object. See MikoPBX\Modules\Setup\PbxExtensionSetupBase::__construct() in Core/src/Modules/Setup/PbxExtensionSetupBase.php, which json_decodes the file and maps its fields onto setup properties.

The field list below was verified against all 60 real module.json manifests in the production Extensions/ tree. Every field documented here appears in at least one shipping module, and the parsing of each is traced to Core PHP. Fields that do not exist (dependencies, max_pbx_version) are called out explicitly in Dependencies are procedural, not declarative.

Running example throughout the docs: a fictional module ModuleBlackList (moduleUniqueID: "ModuleBlackList"). For a minimal real manifest see Extensions/ModuleTemplate/module.json; for a commercial one see Extensions/ModuleAmoCrm/module.json; for a language pack see Extensions/LanguagePacks/ModuleDutchLanguagePack/module.json.

Minimal manifest

Five fields are present in 100% of manifests and are effectively required: developer, moduleUniqueID, support_email, version, and min_pbx_version. The shipping ModuleTemplate manifest is the canonical minimal example.

Extensions/ModuleTemplate/module.json
{
  "developer": "MIKO",
  "moduleUniqueID": "ModuleTemplate",
  "support_email": "help@miko.ru",
  "version": "%ModuleVersion%",
  "min_pbx_version": "2023.2.150",
  "release_settings": {
    "publish_release": true,
    "changelog_enabled": true,
    "create_github_release": true
  }
}

For ModuleBlackList, the equivalent minimal manifest targeting the current baseline:

Core fields

developer

string, required. Human-readable author/vendor name. Mapped to PbxExtensionSetupBase::$developer and written into the developer column of the m_PbxExtensionModules table during registration. Exposed unchanged in the v3 REST API (see the developer field in Core/src/PBXCoreREST/Lib/Modules/DataStructure.php).

moduleUniqueID

string, required. The module's globally unique identifier. This is the single most important field: it is the directory name under the modules root, the PHP namespace segment (Modules\ModuleBlackList\...), the value of the uniqid column in m_PbxExtensionModules, and the key used for every lookup (PbxExtensionModules::findFirstByUniqid()).

Conventions observed across every production manifest:

  • Always starts with the prefix Module.

  • PascalCase, no spaces or punctuation.

  • Matches the module's root directory name exactly.

For ModuleBlackList the directory is Modules/ModuleBlackList/ and the value is "ModuleBlackList".

support_email

string, required. Contact address for the module's author. Mapped to PbxExtensionSetupBase::$support_email. Used for support links in the web UI and the release pipeline; not otherwise interpreted by the PBX.

version

string, required. The module's own version. In source control this is the literal placeholder "%ModuleVersion%" (the value in ModuleTemplate, ModuleAmoCrm, and every language pack). The build/release pipeline substitutes the real semantic version (for example 1.2.0) into the packaged module.json before the ZIP is produced.

At runtime the parsed value is stored in PbxExtensionSetupBase::$version and written to the version column of m_PbxExtensionModules; the REST API surfaces it via the version field in DataStructure.php.

Keep "%ModuleVersion%" verbatim in the repository. Do not hand-edit it to a number — the pipeline owns that substitution, and a hard-coded version will be overwritten or will desynchronise from your release tag.

min_pbx_version

string, required. The only author-declared compatibility constraint in the entire manifest. It states the minimum MikoPBX version the module is allowed to install on.

It is mapped to PbxExtensionSetupBase::$min_pbx_version and enforced by PbxExtensionSetupBase::checkCompatibility(), which installModule() calls first — before license activation, before file installation, before DB migration. If the gate fails, installation aborts.

Behaviour to note:

  • The comparison is a standard PHP version_compare(current, min) < 0. The check is one sided — there is no upper bound (see Dependencies are procedural, not declarative).

  • A trailing -dev suffix on the running PBX version is stripped before comparison, so a developer build compares like its release counterpart.

  • Asymmetric fallback. min_pbx_version is only meaningful if it is present. If the field is missing or empty, PbxExtensionSetupBase::__construct() assigns '' ($module_settings['min_pbx_version'] ?? ''), and version_compare(anything, '') never returns < 0 — so the compatibility gate effectively passes for any PBX version. Note that the property's class-level default is the string '2024.2.3', but the constructor overwrites it with whatever the JSON contains (including the empty string). Always declare min_pbx_version explicitly; never rely on the default.

module_type

string, optional, defaults to "general". Categorises the module for the UI and marketplace. Parsed in PbxExtensionSetupBase::__construct() and stored both on the setup object (public string $module_type) and in the module_type column of m_PbxExtensionModules (default 'general', see PbxExtensionModules.php).

The values documented by Core (the doc-comment on PbxExtensionSetupBase::$module_type) are:

Value
Meaning

general

Default. Ordinary feature module.

languagepack

Web-interface translation pack.

security

Security / firewall / hardening module.

cti

CTI / telephony-integration module.

utility

Utility / tooling module.

call_feature

Call-handling feature.

ai

AI-related module.

The list is open-ended in code (the doc-comment ends with ...), and the field is parsed as a free string. Across the production manifests only languagepack is set explicitly (in all 24 language packs); every other module omits the field and inherits general. Use a value from the table above so the UI categorises your module correctly.

ModuleBlackList is a call-filtering feature, so a reasonable choice is "module_type": "call_feature".

Commercial fields

These two fields appear together in a minority of manifests — currently 14, all commercial MIKO modules, e.g. ModuleAmoCrm). Omit both for a free/open module.

lic_product_id

integer, optional. The MIKO licensing product identifier. Parsed in PbxExtensionSetupBase::__construct() into public $lic_product_id; defaults to 0 when absent. A non-zero value makes the module commercial: activateLicense() requires a valid PBX license key when lic_product_id > 0.

lic_feature_id

integer, optional. The MIKO licensing feature identifier. Parsed into public $lic_feature_id; defaults to 0 when absent. When lic_feature_id > 0, PbxExtensionState::enableModule() calls License::featureAvailable() and refuses to enable the module (recording disableReason = DISABLED_BY_LICENSE) if the feature is not captured.

See Marketplace & licensing for how these IDs are issued and how the activation/feature-capture flow works end to end.

Documentation field

object, optional. Present in a large minority of manifests (currently 23). A map of documentation URLs keyed by language code (ru, en). Parsed in PbxExtensionSetupBase::__construct() into public array $wiki_links (only if the JSON value is an array/object). Surfaced as help links in the web UI. The inner shape is a map of old-url => new-url per language, as seen in ModuleAmoCrm above.

Language-pack fields

Language packs (module_type: "languagepack") carry two extra fields. Both appear in all 24 language-pack manifests and nowhere else.

language_code

string. The locale this pack provides, e.g. "nl-nl", "pt-br". Identifies the translation target inside the PBX. This top-level field is read by Core at runtime — not by PbxExtensionSetupBase::__construct(), but by PbxExtensionUtils::getLanguagePackCode() (in Core/src/Modules/PbxExtensionUtils.php), which returns this value (falling back to the single sub-directory name under sounds/ when the field is absent).

translation_sync

object. Configuration for the tooling that syncs translation strings from the Core repository. Consumed by the release/translation tooling, not by the PBX runtime — there is no reference to it in PbxExtensionSetupBase or PbxExtensionState. Observed shape (from ModuleDutchLanguagePack):

translation_sync keys observed across all 24 packs: enabled (bool), source_repo (string), source_branch (string), language_code (string, the short code used by the sync, distinct from the top-level language_code), and exclude_files (array of file names to skip).

release_settings

object, optional. Present in most manifests (currently 49), always with the identical shape:

This block is build-pipeline metadata only. No Core class reads it; the PBX neither parses nor validates it. It tells the release tooling whether to publish, generate a changelog, and cut a GitHub release. A module without it installs and runs identically. See Building & packaging a module for the release flow.

Dependencies are procedural, not declarative

This is the most common point of confusion, so it is stated plainly:

How inter-module relationships are actually enforced

Module-to-module dependencies are handled procedurally at runtime, through two mechanisms, never through a declared graph:

  1. Lifecycle hooks on the module's config class. PbxExtensionState::enableModule() and disableModule() invoke optional hooks on your BlackListConf config class when they exist (checked with method_exists). The hook names are defined as constants in Core/src/Modules/Config/SystemConfigInterface.php:

    • onBeforeModuleEnable (SystemConfigInterface::ON_BEFORE_MODULE_ENABLE)

    • onAfterModuleEnable (SystemConfigInterface::ON_AFTER_MODULE_ENABLE)

    • onBeforeModuleDisable (SystemConfigInterface::ON_BEFORE_MODULE_DISABLE)

    • onAfterModuleDisable (SystemConfigInterface::ON_AFTER_MODULE_DISABLE)

    If module A needs module B, A's onBeforeModuleEnable is where it checks B's presence/state and refuses or adjusts. There is no manifest field that does this for you.

  2. Model relations + delete guards. PbxExtensionState::makeBeforeDisableTest() walks every model under Modules/<ModuleUniqueID>/Models/, and for models that declare Phalcon relations it runs beforeDelete() against existing records. If another entity still references the module's data, the delete is blocked and disable fails. This is how the PBX prevents, for example, disabling a module whose records are still referenced by the Extensions table — entirely at runtime, via the ORM, with no declared dependency.

What about max_pbx_version?

max_pbx_version exists only as marketplace/release-server metadata. You can see it in Core/src/PBXCoreREST/Lib/Modules/DataStructure.php: it appears in the API data structure built from repository data (createFromRepositoryData(), the available-modules listing) and in the read-only OpenAPI detail schema. It is a readOnly response field populated by the release server — not something an author writes into module.json, and not something checkCompatibility() consults. The compatibility gate enforced on-device is purely the lower bound (min_pbx_version).

Practical takeaway: declare a single honest min_pbx_version, and implement any cross-module requirements in your config class hooks and model relations. Do not look for (or invent) a dependencies or max_pbx_version field — they will be ignored.

Field summary

Field
Type
Required
Parsed by PBX
Where it goes / who reads it

developer

string

yes

yes

PbxExtensionSetupBase::$developer, m_PbxExtensionModules

moduleUniqueID

string

yes

yes

directory, namespace, uniqid column, all lookups

support_email

string

yes

yes

PbxExtensionSetupBase::$support_email

version

string

yes

yes

$version; %ModuleVersion% placeholder filled by pipeline

min_pbx_version

string

yes

yes

checkCompatibility() lower-bound gate

module_type

string

no

yes

$module_type / module_type column (default general)

lic_product_id

integer

no

yes

$lic_product_id; gates activateLicense()

lic_feature_id

integer

no

yes

$lic_feature_id; gates enableModule() feature capture

wiki_links

object

no

yes

$wiki_links; UI help links

language_code

string

no*

yes

PbxExtensionUtils::getLanguagePackCode() (*required for language packs)

translation_sync

object

no*

no

translation tooling (*language packs only)

release_settings

object

no

no

release/build pipeline only

dependencies

does not exist; relationships are procedural

max_pbx_version

not an author field; marketplace metadata only

Last updated

Was this helpful?