Modules and reusable capabilities
The core of the platform serves every MDA. Some MDAs need functions nobody else needs: a ministry's estate and rental management, forestry concessions, co-operative registration. These live in modules. Other functions could serve many MDAs: selling goods, quotas, inspections. These become reusable capabilities. This lesson explains both, and how to decide which one a need calls for.
What a module is
A module is an optional, versioned bundle that an MDA can switch on without changing core. It owns:
- its own tables (migrations), routes and controllers;
- its policy rules, named actions checked by the policy decision point;
- its background jobs;
- its back-office screens and menu entries, under "Ministry functions";
- its contributions to core through extension points;
- optionally, generic data-driven blocks for the portal and capabilities for the POS app.
In code, a module lives in zcm-backend/modules/<id>/ with a manifest (module.js), and its back-office screens in back-office-ui/src/modules/<id>/. New modules are created with scripts/newModule.js, and a worked example module shows every part.
Core never imports a module. A module plugs into core; core does not know which modules exist.
Switching modules on and off
A module is off by default. It is on for a caller only when every level says yes:
| Level | Set by | Example |
|---|---|---|
| 1. Ceiling | Environment or Helm values (MODULES_AVAILABLE, MODULES_FORCE_OFF) | The test environment never offers module X |
| 2. Installation | An administrator, under System administration โ Modules, with maker-checker | Module X is switched on for this installation |
| 3. Ministry assignment | The same screen | Module X is on only for the MDAs it is assigned to |
A module whose dependency is off is off too. On the hub, a module is never assigned to a ministry that runs its own node: that ministry's node runs it.
Off means absent, but nothing is lost
When a module is off:
- every one of its routes answers
404 MODULE_DISABLED; - its policy actions are refused at the very first step of every decision;
- its jobs are skipped, and its menu entries and portal blocks disappear;
- its data stays. Switching it on again restores everything.
There is one important exception, the money rule: a payment that was started while the module was on is still finalised by the module after it is switched off. Switching a module off must never lose money or records.
Extension points
Modules plug into core only through named extension points, listed in lib/modules/points.js. Some examples:
| Extension point | What a module can do through it |
|---|---|
case.onSubmit | React when an application is submitted |
case.onDecision, case.onApproved | React to a decision |
payment.quote | Compute a fee from its own rules |
payment.onFinalised | Act when a payment is finalised (also when the module is off) |
geo.validators | Check a geometry, for example that an area does not overlap another licence |
allocation.claims | Claim units in the allocation and quota ledger |
registers.packs | Contribute register packs (typed registers) |
documents.templates | Contribute output-document templates |
designer.stepTypes | Offer a new step type in the process designer |
dsr.exportSections | Add its data to a citizen's data access export |
If a module needs a hook that does not exist, the answer is to add a new generic extension point to core and document it. Adding module logic to core files such as the case controller or the workflow engine is not allowed.
Reusable capabilities
When a feature could serve more than one MDA, it is built once, in core, and switched on per service through a service capability or setting. Examples already built:
- the shop, for any ministry that sells products (a product catalogue for any service with the
goods-salecapability);
- the allocation and quota ledger;
- name reservation;
- inspections;
- enforcement cases;
- the geometry field, for any licence that covers an area.
A capability is cheaper for everyone: one implementation to test and secure, and any MDA can use it by configuration.
The portal stays generic
Modules may add only generic, data-driven blocks to the portal. For example:
- a map and area block for any licence that carries a geometry;
- a product catalogue for any service with the
goods-salecapability.
A guard test with an allow-list enforces this, and nobody adds to the allow-list. Ministry-specific screens belong only in the back office.
Deciding: configuration, capability or module
| Need | Answer |
|---|---|
| A new licence with a form, an inspection, a fee and a certificate | Configuration only |
| Several MDAs sell goods to the public | The shop capability, switched on per service |
| One ministry manages its own estate of government houses, leases and rent billing | A module |
| A module needs to compute a fee from a register it owns | The module, through payment.quote |
| A module needs to react to an event core does not announce yet | A new generic extension point in core, then the module uses it |
| A citizen page "My forest licences" | No. Licences appear under the generic "My licences / permits" |
Example
An MDA wants applicants to reserve a business name before registering. Name reservation is useful to several registries, so it exists as a reusable capability. The MDA switches it on for its service and configures the rest. If the same MDA also needed a register of its own inspectors' equipment, used by nobody else, that would be a module, with its own tables and screens under Ministry functions, switched on only for that MDA.
Check your understanding
- What is the default state of a module, and who switches it on?
- What does every route of a disabled module answer?
- What is the money rule, and why does it exist?
- What should you do if a module needs a hook core does not offer?
- Give two examples of reusable capabilities and say why they are not modules.
Sign in to record your progress.
Previous: Federation: one portal, many back offices Next: Security, evidence and environments