Docs
Developer Documentation

Models

Define an extension's database tables and get async CRUD for free.

A model is a database table. Extend Models, give it a name, and describe its columns in schema(). Models live in src/models/ and load from every *.js file in that folder.

src/models/Products.js
import { Models } from "#kora/extensions";

export class Products extends Models {
    constructor(kora) {
        super(kora, { name: "Products" });
    }

    schema() {
        return {
            name: { type: "string", required: true, unique: true },
            price: { type: "float" },
            active: { type: "boolean", default: true }
        };
    }
}

Each field is { type, required?, unique?, default? }, or just a type string for a plain column (price: "float"). Supported types:

TypeStored as
stringshort text
textlong text
integerwhole number
bigintlarge whole number (use for Discord IDs and other 64-bit values)
floatdecimal number
booleantrue / false
datetimestamp
jsonarbitrary JSON

CRUD

Models come with async methods you call from handlers:

const model = this.kora.extensions.models.get(this.extension, "Products");

await model.create({ name: "Basic", price: 4.99 });
await model.get({ name: "Basic" });     // one row, or null
await model.list();                     // all rows (pass a filter object to narrow)
await model.edit({ name: "Basic" }, { price: 5.99 });
await model.delete({ name: "Basic" });  // returns the number of rows removed

Unique-constraint and validation failures are translated into friendly errors with an HTTP status (409 for duplicates, 400 for missing required fields), so they surface cleanly through routes.

Dynamic fields

Set dynamic: true to let a model accept fields that are not in its schema. Anything unknown is stored in a JSON options column instead of being rejected, which is how the core extension lets you add config-defined fields (like a customer email) without changing the table.

super(kora, { name: "Customers", dynamic: true });

Schema changes on restart

When you change a model's schema(), Kora applies the change on the next restart, as long as DATABASE.MIGRATE is on. Each table is synced independently, so a problem with one model never blocks the others. When a change cannot be applied safely (for example, making a column required while existing rows have no value for it), Kora logs a warning explaining why and leaves that table as it was, rather than failing the whole startup. Fix the underlying data and restart to let the change through.

On this page