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.
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:
| Type | Stored as |
|---|---|
string | short text |
text | long text |
integer | whole number |
bigint | large whole number (use for Discord IDs and other 64-bit values) |
float | decimal number |
boolean | true / false |
date | timestamp |
json | arbitrary 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 removedUnique-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.

