Prism/Overview

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/
FolderFiles loadedBase class
main.jsThe moduleModules
src/models/Every *.jsModels
src/handlers/Every *.jsHandlers
src/routes/Every route.js, at any depthRoutes
src/clocks/Every *.jsClocks
src/events/Every *.jsEvents
resources/config/Every *.jsConfigs
resources/lang/en/*.js classes, plus a folder of JSON per localeLangs
resources/web/pages/Every page.js, at any depthPages

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.

  1. Register. Install any missing dependencies.node packages, then read configs, languages, models, routes and pages.
  2. 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

modules/example/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.`);
    }
}
KeyDefaultWhat it does
nameRequiredThe module ID. Use the lowercase folder name.
displayNamenameFallback label. Translate the name with module.<id>.name instead. See Languages.
author, version, descriptionNoneShown on the Modules page.
iconpuzzleA 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.

MethodCalled
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.

PropertyWhat it is
this.prism.databaseThe database connection. this.prism.database.sequelize is the Sequelize instance.
this.prism.settingsValues from the Settings and Theme pages.
this.prism.consoleThe logger.
this.prism.auditThe audit log.
this.prism.signerSigns and checks payloads with the response signing key.
this.prism.mailSends email through the SMTP settings.
this.prism.storageUploads to and signs links for the S3 bucket.
this.prism.eventsThe event bus. See Events.
this.prism.modulesEvery loaded module and its parts.
this.prism.apiThe 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;