Overview
How a Prism module is laid out, how it loads and what it can hook into.
Everything Prism does at runtime comes from a module, including Core. Your own modules load the same way and get the same access to the database, API, dashboard, audit log and languages.
For installing and running Prism, see the Setup Guide.
Layout
Each folder in modules/ is one module. Prism imports its main.js and loads everything under src/ and resources/ by folder.
modules/
example/
main.js
resources/
config/
lang/
web/
pages/
src/
clocks/
events/
handlers/
models/
routes/| Folder | Files loaded | Base class |
|---|---|---|
main.js | The module | Modules |
src/models/ | Every *.js | Models |
src/handlers/ | Every *.js | Handlers |
src/routes/ | Every route.js, at any depth | Routes |
src/clocks/ | Every *.js | Clocks |
src/events/ | Every *.js | Events |
resources/config/ | Every *.js | Configs |
resources/lang/ | en/*.js classes, plus a folder of JSON per locale | Langs |
resources/web/pages/ | Every page.js, at any depth | Pages |
Import every base class from #prism/modules. Each file exports one class. Code goes in src/, config and setup in resources/.
Loading
Prism loads modules in two passes. Core always goes first, then every other module after the modules it requires.
- Register. Install any missing
dependencies.nodepackages, then read configs, languages, models, routes and pages. - Load. Create the tables, start handlers, events and clocks, call
onStartup()and mount routes.
When Prism stops, it stops clocks, removes events and calls onShutdown() for each module in reverse order.
A module that throws while loading is marked as failed and shown on the Modules page and the Overview. The others keep running. If Core fails, Prism stops.
main.js
import { Modules } from "#prism/modules";
export class Example extends Modules {
constructor(prism) {
super(prism, {
name: "example",
author: "Your Name",
version: "1.0.0",
description: "Does one thing well",
icon: "box",
requires: ["core"],
dependencies: { node: ["nanoid"] }
});
}
permissions() {
return [{ id: "example.view", label: "View examples" }, { id: "example.edit", label: "Edit examples" }];
}
async onStartup() {
this.prism.console.log(this.prism.console.levels.info, `${this.getName()} started.`);
}
}| Key | Default | What it does |
|---|---|---|
name | Required | The module ID. Use the lowercase folder name. |
displayName | name | Fallback label. Translate the name with module.<id>.name instead. See Languages. |
author, version, description | None | Shown on the Modules page. |
icon | puzzle | A Lucide icon name. |
requires | [] | Modules that must load first. Missing ones fail this module. |
dependencies.node | [] | npm packages Prism installs before loading the module. |
Hooks
Add any of these methods to your module class.
| Method | Called |
|---|---|
onStartup() | After the module loads. |
onShutdown() | Before it unloads. |
permissions() | When Prism lists permissions. Return [{ id, label }]. They show up when editing roles and API tokens. |
fields(module, page) | When another module's page is described. Return extra form fields for it. |
decorate(module, page, rows) | When another module's page lists or creates rows. Return the rows with your data added. |
The Kora module uses fields and decorate to add a Kora product picker to Core's product form:
fields(module, page) {
if (module !== "core" || page !== "products") return [];
return [{ key: "kora_product", label: "Kora product", type: "reference", source: { module: "kora", endpoint: "/products", label: "name", value: "id" }, empty: "No products found" }];
}Core also runs a customer's licenses through decorate("core", "accountLicenses", licenses) before GET /api/v1/account/licenses returns them. Each license has a fields array that the My Licenses card shows under the product. Push { key, label, value, copy } entries onto it, where label is a translation key from your module's common.json and copy adds a copy button. Kora uses this to show the Kora license key issued for each purchase:
decorate(module, page, rows) {
if (module !== "core" || page !== "accountLicenses") return rows;
return rows.map(row => ({ ...row, fields: [...row.fields, { key: "kora_key", label: "account.licenses.field.kora_key", value: "KORA-XXXX", copy: true }] }));
}The prism Object
Every class gets the running instance as this.prism.
| Property | What it is |
|---|---|
this.prism.database | The database connection. this.prism.database.sequelize is the Sequelize instance. |
this.prism.settings | Values from the Settings and Theme pages. |
this.prism.console | The logger. |
this.prism.audit | The audit log. |
this.prism.signer | Signs and checks payloads with the response signing key. |
this.prism.mail | Sends email through the SMTP settings. |
this.prism.storage | Uploads to and signs links for the S3 bucket. |
this.prism.events | The event bus. See Events. |
this.prism.modules | Every loaded module and its parts. |
this.prism.api | The API server and permission checks. |
See Utilities for each one.
Reaching Other Modules
this.prism.modules.models.get("core", "products");
this.prism.modules.handlers.get("core", "licenses");
this.prism.modules.config.values("kora", "kora");
this.prism.modules.lang.phrase("core", "messages", "not_found");get returns null when that module is not loaded.
Addons
Paid extras on the Prism license, such as Watermark, are checked with:
if (this.prism.hasAddon("Watermark")) return;