Overview
What a Kora extension is, how it loads, and the building blocks you get to work with.
Everything Kora does at runtime lives in an extension. The licensing features you use out of the
box are the official extensions (starting with core); an extension you write is loaded exactly the
same way and gets the same building blocks. This section shows you how to write your own.
This is the guide for building your own extension: new data, API endpoints, CLI commands, or dashboard pages. Looking for what the official extensions do? See the Extensions section. Only want to configure the licensing features? See the Configuration guide.
How extensions are loaded
Every folder inside extensions/ is one extension. On startup Kora reads each folder's main.js,
constructs the class it exports, then scans that folder's src/ for the building blocks below and
registers whatever it finds. Nothing is wired up by hand: drop a file in the right folder and it loads.
extensions/
my-extension/
main.js # the extension class (required)
src/
models/ # database tables
handlers/ # business logic
routes/ # HTTP API endpoints
cli/
actions/ # what a CLI command does
commands/ # how a CLI command is called
dashboard/
pages/ # dashboard nav entries
components/ # dashboard React components
routes/ # dashboard server actionsEach building block loads slightly differently:
| Folder | Loaded from | Nesting |
|---|---|---|
models, handlers | every *.js file directly in the folder | flat |
cli/actions, cli/commands | every *.js file directly in the folder | flat |
routes | every route.js, at any depth | nested |
dashboard/pages | every page.js, at any depth | nested |
dashboard/components, dashboard/routes | copied into the dashboard at build time (see Dashboard Pages) | any |
Each loaded file must export one class, and Kora identifies it by the name you pass to super(),
not by the filename. Keep names unique within each building block for a single extension.
The extension class
main.js exports a class that extends Extensions. This is where you declare who you are and hook into
the startup and shutdown lifecycle.
import { Extensions } from "#kora/extensions";
export class MyExtension extends Extensions {
constructor(kora) {
super(kora, {
name: "my-extension",
author: "Your Name",
version: "1.0.0.0",
dependencies: { hard: "", soft: "", node: "" }
});
}
onStartup() {
this.kora.console.log(this.kora.console.levels.startup, `${this.getName()} started up.`);
}
onShutdown() {
this.kora.console.log(this.kora.console.levels.info, `${this.getName()} shut down.`);
}
}nameis the extension's identity. It is also the folder-relative key you pass to thekora.extensions.*getters shown throughout this guide.onStartup()runs after the extension's models, handlers, routes, and CLI are loaded but before its models are defined and routes mounted. It is the place to register config files.onShutdown()runs when Kora stops or when the extension is unloaded after a failure.
#kora/extensions is an import alias Kora provides. Every base class in this guide
(Extensions, Models, Handlers, Routes, Actions, Commands, Pages) is imported from it.
The kora object
Every building block receives the shared kora instance in its constructor and stores it as
this.kora. It is your gateway to the rest of the system:
| Path | What it gives you |
|---|---|
this.kora.config | Read and register config files. |
this.kora.console | Structured logging. |
this.kora.extensions.models.get(ext, name) | A model instance. |
this.kora.extensions.handlers.get(ext, name) | A handler instance. |
this.kora.extensions.routes.get(ext, name) | A route instance. |
this.kora.extensions.actions.get(ext, name) | A CLI action instance. |
this.kora.extensions.commands.get(ext, name) | A CLI command instance. |
this.kora.extensions.pages.list() | All dashboard pages, across every extension. |
this.kora.backend.database | The Sequelize instance (models use this for you). |
this.kora.backend.signature | Sign and verify payloads with HMAC. |
this.kora.events | Subscribe to runtime events such as api.request. |
Inside any loaded building block, this.extension is your own extension's name, so you cross-reference
your own pieces like this.kora.extensions.handlers.get(this.extension, "Customers").
The building blocks
Each capability has its own page:
- Models define database tables and give you CRUD.
- Handlers hold your business logic.
- Routes expose your extension over HTTP.
- CLI Commands add commands to the interactive console.
- Dashboard Pages add pages to the web dashboard.
- Utilities are the cross-cutting helpers every building block shares: config files, logging, request events, and signing.
Putting it together
A minimal extension that adds a products table, an API endpoint, and a CLI command needs these files:
extensions/my-extension/
main.js
src/
models/Products.js
handlers/Products.js
routes/products/route.js
cli/commands/products.js
cli/actions/products.jsAdd a dashboard page for it by dropping in src/dashboard/pages/products/page.js,
src/dashboard/components/products.tsx, and src/dashboard/routes/products.ts. Restart Kora, and your
model syncs, your endpoint mounts at /api/products, your products command appears in the console, and
your page shows up in the dashboard sidebar, no changes to Kora itself required.

