Add an entity to a feature
You already have a feature with one entity. You want to add a second one — same defaults, full CRUD, a list screen, an edit screen, and a nav entry.
Prerequisites
Section titled “Prerequisites”- A feature already running locally. The Walkthrough covers the first one.
- You’ve read Schemas as data so the field factories make sense.
The code
Section titled “The code”In your feature’s schema/ folder, define the new entity:
import { createEntity, createTextField } from "@cosmicdrift/kumiko-framework/engine";
export const tagEntity = createEntity({ fields: { name: createTextField({ required: true, sortable: true, searchable: true }), color: createTextField({ default: "#888888" }), },});Wire it into the feature alongside the existing entity:
import { defineEntityCreateHandler, defineEntityDeleteHandler, defineEntityDetailHandler, defineEntityListHandler, defineEntityUpdateHandler,} from "@cosmicdrift/kumiko-framework/engine";import { tagEntity } from "./schema/tag";
export const notesFeature = defineFeature("notes", (r) => { // existing note entity stays r.entity("note", noteEntity);
// new tag entity r.entity("tag", tagEntity);
r.writeHandler(defineEntityCreateHandler("tag", tagEntity)); r.writeHandler(defineEntityUpdateHandler("tag", tagEntity)); r.writeHandler(defineEntityDeleteHandler("tag", tagEntity)); r.queryHandler(defineEntityListHandler("tag", tagEntity)); r.queryHandler(defineEntityDetailHandler("tag", tagEntity));
r.nav({ id: "tags", label: "notes:nav.tags", order: 20, screen: "tag-list" });});Add list and edit screens (same shape as the note ones), translations for the new keys, and you’re done. Hot-reload picks it up.
What you get for free
Section titled “What you get for free”The framework derives a Drizzle table, a Zod schema, the default access rules, list pagination, sortable columns, and a search index entry — all from the entity definition. You wrote the schema; the framework wrote the rest.
If you need custom behaviour on save (a slug from a name, validation across multiple fields), drop in a hook:
r.hook("preSave", "tag", async (ctx, { changes, data }) => { if (changes.name && !changes.color) { return { ...data, color: stringToHexColor(data.name) }; } return data;});The hook runs in the same transaction as the write — if it throws, the whole thing rolls back. See Lifecycle and hooks for the contract.
Common gotchas
Section titled “Common gotchas”- Entity names are kebab-case by convention. Multi-word entities
like
user-profilework;userProfiledoes not (the boot validator rejects it). r.entityand the screen registration must use the same name. Addingr.entity("tag", …)but registeringtagListScreenwithentity: "tags"(plural) gives you a “no such entity” boot error.- Cross-feature lookups go through the dispatcher. If your tag
entity is referenced by a
notefield withtype: "reference", the reference resolves through the framework — no manual join.
Live example
Section titled “Live example”A complete feature wiring one entity with default CRUD plus soft-delete/restore — the smallest useful end-to-end:
export const taskFeature = defineFeature("tasks", (r) => { r.entity("task", taskEntity);
// Writes append CRUD-style events onto the task stream and update the // projection row in the same TX (the executor inside the helper takes care // of both). Custom logic? Replace any single line with an explicit // r.writeHandler. r.writeHandler(defineEntityCreateHandler("task", taskEntity, editorWrite)); r.writeHandler(defineEntityUpdateHandler("task", taskEntity, editorWrite)); r.writeHandler(defineEntityDeleteHandler("task", taskEntity, adminWrite)); r.writeHandler(defineEntityRestoreHandler("task", taskEntity, adminWrite));
// Reads served from the projection table. r.queryHandler(defineEntityListHandler("task", taskEntity, openRead)); r.queryHandler(defineEntityDetailHandler("task", taskEntity, openRead));});Full source: samples/recipes/basic-entity.
See also
Section titled “See also”- Schemas as data — what the entity definition is and what gets derived.
- Commands and queries — the pipeline around the auto-generated CRUD handlers.
- Walkthrough — the full first-feature tutorial if you don’t yet have a feature to extend.