Skip to content

Walkthrough, customize your starter app

If you ran the Quickstart, you already have a running app with a Tasks list in the sidebar. This walkthrough explains what the starter generated and how to change it. If you customized it in Make it yours, those changes stay in place.

Kumiko-generated admin UI: a task list with sortable columns and a status column

What the starter includes. kumiko new app scaffolds a demo tasks feature: entity, CRUD handlers, list + edit screens, sidebar nav, and seeded rows. You declare the shape once; Kumiko derives the table, API, access rules, and admin UI.

At the end you understand:

  • which files the scaffold created and what each does
  • how the tasks feature wires entity → handlers → screens → nav
  • how to add a field and see it in the UI after bun dev
  • how to run the tests the scaffold ships with

Prerequisite: a scaffolded app with bun dev working. Follow the Quickstart first if you do not have one yet.

Step 1, Scaffold (or use your Quickstart app)

Section titled “Step 1, Scaffold (or use your Quickstart app)”

Use the same installer as the Quickstart:

Terminal window
curl -fsSL https://kumiko.rocks/install.sh | bash -s -- my-app --yes
cd my-app
bun install

If Bun is already installed, the CLI can scaffold the app without the installer script. It has no feature picker, so the feature list differs (see below):

Terminal window
bunx @cosmicdrift/kumiko-cli@latest new app my-app

The workspace includes src/features/tasks/feature.ts (full demo feature), src/seed.ts (demo rows for bun dev), bin/dev.ts, src/client.tsx, .env.example, and docker-compose.yml.

src/run-config.ts lists the features the app mounts and always ends with tasksFeature. With --yes, the installer puts the recommended features from the feature picker there. kumiko new app has no picker and mounts secrets, auth-foundation and sessions. In both cases config, user, tenant and auth-email-password are left out of the list, because composeFeatures mounts them automatically when bin/main.ts passes auth: { admin: { … } }.

Terminal window
cp .env.example .env

Write two random local keys into .env. This is the same command as in the Quickstart. It replaces the placeholders in the file and prints nothing:

Terminal window
JWT_SECRET="$(openssl rand -base64 32)"
MASTER_KEY="$(openssl rand -base64 32)"
sed -i.bak \
-e "s|^JWT_SECRET=.*$|JWT_SECRET=${JWT_SECRET}|" \
-e "s|^KUMIKO_SECRETS_MASTER_KEY_V1=.*$|KUMIKO_SECRETS_MASTER_KEY_V1=${MASTER_KEY}|" \
.env
rm .env.bak
unset JWT_SECRET MASTER_KEY

Then validate without a database:

Terminal window
bun run boot
[runProdApp] boot validation OK (<n> features, <n> registry entries)

This validates the mounted feature graph and environment schema without opening Postgres or Redis. Expect a line containing boot validation OK.

Terminal window
docker compose up -d
bun dev

The welcome banner prints the URL (default http://localhost:4173) and admin login. Sign in as admin@my-app.local / changeme, open Tasks, demo rows are already there:

Kumiko-generated edit form: typed fields with a boolean toggle and a number field
FileWhat it does
src/run-config.tsWhich features your app mounts (APP_FEATURES).
src/features/tasks/feature.tsEntity + handlers + screens + nav for the demo.
src/seed.tsInserts demo tasks on first bun dev (idempotent).
bin/dev.tsDev server + welcome banner + seeds: [seedDemoTasks].
bin/main.tsProduction bootstrap (bun run start).

Open src/features/tasks/feature.ts. The starter already declares:

  • taskEntity, fields (title, status, priority, isUrgent)
  • CRUD handlers, defineEntity*Handler for create/update/delete/list/detail
  • listScreen + editScreen, generated admin UI definitions
  • r.nav(...), sidebar entries pointing at those screens

Add a notes text field to see schema-driven UI updates:

// In taskEntity.fields:
notes: createTextField({ personal: false, reason: "is_business_data" }),

Every text field states whether it holds personal data. Kumiko uses that stance for the GDPR data export and for erasing a user’s data, so a text field without it stops boot. personal: false with a reason says the notes are business data. A field that holds data about a person declares the subject instead, for example personal: "self", find: "fuzzy". The starter’s title and status fields use the same form; see Crypto-shredding for all options.

Add "notes" to listScreen.columns and editScreen.layout.sections[0].fields. Then give it a label in src/features/tasks/i18n.ts. The file has two objects, one for the server and one for the browser, and the key goes into both:

  • tasksTranslationKeys: "tasks:entity:task:field:notes": { de: "Notizen", en: "Notes" }
  • tasksTranslations: "tasks:entity:task:field:notes": "Notizen" under de and "Notes" under en

Save and let bun dev restart. The development server creates local tables for this workflow. A production app needs a checked-in migration generated with bunx kumiko-schema generate <name> before deployment.

Re-run boot to confirm composition still validates:

Terminal window
bun run boot

The scaffold already wrote two tests. src/__tests__/run-config.test.ts runs boot validation over the mounted features. src/__tests__/tasks.integration.test.ts starts a real test stack against Postgres and checks that a task stays inside its tenant:

describe("tasks", () => {
test("a created task is listed for its own tenant only", async () => {
const owner = await seedTenant(stack);
const other = await seedTenant(stack);
await owner.api.writeOk(TASK_CREATE, { title: "Write tests", status: "todo", priority: 1 });
const ownerTasks = await owner.api.queryOk<TaskList>(TASK_LIST, {});
expect(ownerTasks.rows.map((row) => row.title)).toEqual(["Write tests"]);
const otherTasks = await other.api.queryOk<TaskList>(TASK_LIST, {});
expect(otherTasks.rows).toEqual([]);
});
});

The test file is generated from packages/testing/src/scaffold.ts. Run the unit tests, then the integration tests with Postgres running:

Terminal window
bun run test
docker compose up -d
bun run test:integration

If you added notes in Step 4, extend the TASK_CREATE payload with a notes value and assert it in the list. The testing standard explains the rest of the setup.

The starter’s tasks feature is yours to edit or delete. For a second domain feature, use the CLI (scaffolds a bare entity, add screens when you need UI):

Terminal window
bunx @cosmicdrift/kumiko-cli add feature notes

This creates src/features/notes/ and auto-mounts it in src/run-config.ts. Add navigation and translations before opening its screen, then run bun run boot.

If something does not match this doc, open an issue.