Docs
Developer Documentation

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 actions

Each building block loads slightly differently:

FolderLoaded fromNesting
models, handlersevery *.js file directly in the folderflat
cli/actions, cli/commandsevery *.js file directly in the folderflat
routesevery route.js, at any depthnested
dashboard/pagesevery page.js, at any depthnested
dashboard/components, dashboard/routescopied 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.

extensions/my-extension/main.js
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.`);
    }
}
  • name is the extension's identity. It is also the folder-relative key you pass to the kora.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:

PathWhat it gives you
this.kora.configRead and register config files.
this.kora.consoleStructured 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.databaseThe Sequelize instance (models use this for you).
this.kora.backend.signatureSign and verify payloads with HMAC.
this.kora.eventsSubscribe 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.js

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

On this page