How modules work¶
All of Didacta's functionality beyond the core — courses, assessments, certificates, community, payments, virtual classroom… — lives in modules. The core provides the platform (tenancy, auth, storage, events, auditing); the modules provide the product.
Modules are always Community
No module is ever locked behind a license. The only paid features in Didacta are cross-cutting core capabilities. This is a non-negotiable design rule of the project.
Two levels of module¶
Didacta distinguishes two levels with different contracts:
| First-party (in-tree) | Third-party (marketplace) | |
|---|---|---|
| Where it lives | In the repository, under modules/<slug>/ |
In a signed ZIP uploaded by a super_admin |
| Trust | Product code, reviewed in the repository | External code, executed in a sandbox (isolated VM) |
| Data access | The host's Prisma; it may read (never write) other modules' tables by declaring the dependency | Only its own tables, through a sandboxed db with a prefix-based SQL guard |
| UI | In-tree, in the Didacta web app | JS bundles inside the ZIP |
The 24 modules Didacta ships are first-party. The third-party level exists for the future marketplace — see Third-party modules.
Isolation rules¶
Every module, whatever its level, honours the same contract:
- Its own tables, prefixed
mod_<slug>_, with atenant_idcolumn + an RLS policy (automatic — see Database). - Zero foreign keys between tables of different modules.
- Zero imports of another module's private code.
- Communication between modules goes through domain events, hooks or public APIs — and every event emitted or consumed is declared in the manifest.
- A first-party module may read another module's tables if it declares the dependency in its manifest and always filters by
tenant_id. Writing, never.
The module context¶
A module does not reach infrastructure directly: it receives a ModuleContext carrying the core services —
eventBus (domain events with a transactional outbox) · hookRegistry (synchronous extension points) · storage · auditLog · evidenceVault · notificationHub (email / in-app / webhook, honouring preferences) · i18n · logger · config (per-tenant, per-module configuration).
Activation per tenant¶
Modules are enabled and disabled per organization:
- The state lives in the database (
tenant_module); with no record, the module'senabledByDefaultapplies. - Modules in the core category (courses, learning, assessments, certificates…) cannot be disabled (the API returns 422
CORE_MODULE_NOT_DISABLEABLE). - With a module disabled, its endpoints (
/api/v1/modules/<slug>/…) return 403 and its UI disappears from the menu.
See Managing modules.
Dependencies¶
A manifest declares hard dependencies (dependencies.modules — without them the module does not start) and soft ones (optionalModules — documenting optional integrations). The registry orders loading topologically and detects cycles and incompatible versions. Disabling a module others depend on requires an explicit cascade (force).