Configuration
How config is split between vite.config.ts, src/middleware.ts, and wrangler.jsonc
Koze has no project-level config file. The framework is convention-driven for everything it can be (route discovery, DOs, workflows, queues, pipelines, containers, sandboxes, agents), and where configuration genuinely is needed, it splits cleanly across three honest surfaces:
| File | Owns | Edited by you |
|---|---|---|
vite.config.ts |
Build-time options for the Koze plugin (security headers, directory overrides) | Yes |
wrangler.jsonc |
Cloudflare deployment (secrets, routes, custom bindings) | Yes, except auto-synced fields (DOs, workflows, queues, pipelines, containers, sandboxes, assets) |
src/middleware.ts |
Request-time concerns (auth, ORM auto-migration, custom steps) | Yes |
If you used kuratchi.config.ts in a previous version, see Migrating from kuratchi.config.ts below.
vite.config.ts
Where the Koze plugin lives. Pass options here for things that need to be baked into the build:
import { defineConfig } from 'vite';
import { cloudflare } from '@cloudflare/vite-plugin';
import { koze } from '@kuratchi/koze/vite';
export default defineConfig({
plugins: [
koze({
// Optional. All defaults are sensible.
// routesDir: 'src/routes',
// serverDir: 'src/server',
// libDir: 'src/lib',
security: {
contentSecurityPolicy:
"default-src 'self'; script-src 'self' 'nonce-{NONCE}'",
strictTransportSecurity:
'max-age=31536000; includeSubDomains',
permissionsPolicy:
'camera=(), microphone=(), geolocation=()',
},
}),
cloudflare({ viteEnvironment: { name: 'ssr' } }),
],
});
Available options:
| Option | Default | Purpose |
|---|---|---|
routesDir |
'src/routes' |
Where .koze route files live |
serverDir |
'src/server' |
Where DOs / workflows / containers / queues / pipelines / sandboxes / agents are auto-discovered |
libDir |
'src/lib' |
Resolution root for $lib/* imports |
security.contentSecurityPolicy |
null |
CSP header value. Use {NONCE} to opt into per-request nonce stamping on injected <script> tags. |
security.strictTransportSecurity |
null |
HSTS header value |
security.permissionsPolicy |
null |
Permissions-Policy header value |
apiShield |
true |
Generate _cloudflare/api-shield/openapi.json from route-adjacent *.api-shield.ts files and legacy API route manifests. Pass { title, version, servers, include, outputPath } to customize, or false to disable. |
Server convention suffixes
Files under serverDir use singular <name>.<kind>.ts suffixes. Plural forms such as *.agents.ts, *.workflows.ts, or *.queues.ts fail dev/build with a fix-it error.
| Suffix | Purpose | Wrangler handling |
|---|---|---|
*.do.ts |
Durable Object class | Auto-synced |
*.workflow.ts |
Cloudflare Workflow class | Auto-synced |
*.queue.ts |
Queue consumer | Auto-synced consumer dispatch |
*.pipeline.ts |
Cloudflare Pipelines binding | Auto-synced |
*.container.ts |
Cloudflare Container class | Auto-synced |
*.sandbox.ts |
Cloudflare Sandbox class | Auto-synced |
*.agent.ts |
Agents SDK class | Re-exported; add the Durable Object binding/migration in Wrangler |
src/middleware.ts
Where everything request-time composes. Each export is a MiddlewareStep with request, route, response, and error hooks.
import { defineMiddleware } from 'koze:middleware';
import { autoMigrate } from '@kuratchi/kunii';
import { kyzenAuthMiddleware } from '@kuratchi/kyzen/middleware';
import { adminSchema } from './server/schemas/admin';
import { authConfig } from './server/auth-config';
export default defineMiddleware({
// Apply pending D1 migrations on cold start.
// Idempotent — runs once per worker isolate.
migrate: autoMigrate({ DB: adminSchema }),
// Sessions, guards, OAuth, rate-limit, turnstile.
auth: kyzenAuthMiddleware(authConfig),
// Your own steps:
logging: {
async request(ctx, next) {
const start = Date.now();
const res = await next();
console.log(`${ctx.request.method} ${ctx.url.pathname} ${Date.now() - start}ms`);
return res;
},
},
});
Both autoMigrate and kyzenAuthMiddleware are ordinary middleware steps — there are no special framework slots. The same shape lets you swap in any third-party auth / ORM / observability tool. See Middleware for the full lifecycle.
wrangler.jsonc
Cloudflare deployment config. You own everything except the fields the Koze compiler auto-syncs at build time:
| Auto-synced | Don't edit |
|---|---|
durable_objects.bindings |
Yes |
migrations[].new_sqlite_classes |
Yes |
workflows[] |
Yes |
containers[] |
Yes |
pipelines[] |
Yes |
queues.consumers |
Yes |
assets.directory, assets.binding |
Yes |
| Everything else (routes, secrets, env, KV, R2, D1, custom bindings) | No |
Drop binding names into your code (env.DB, env.MY_KV) and add the matching wrangler entry. Secrets go through wrangler secret put or .dev.vars.
CSS and global stylesheets
There is no CSS option in any config — CSS is a convention. Drop a file at src/app.css and Koze registers it as a Vite client entry, runs every CSS plugin (Tailwind, PostCSS, Lightning CSS), and injects <link rel="stylesheet"> into the app shell before </head>.
/* src/app.css */
@import 'tailwindcss';
@plugin 'daisyui';
@source './routes/**/*.koze';
For kuzan, import its theme stylesheet from src/app.css:
@import '@kuratchi/kuzan/styles/theme.css';
See Styling for Tailwind, DaisyUI, and the static-assets path.
kuzan integration
kuzan is a regular package — there is no framework-level wiring. Three steps:
- Install:
npm install @kuratchi/kuzan. - Import the theme stylesheet from
src/app.css(above). - Add
<ThemeInit />to your layout's<head>to prevent flash-of-wrong-theme on hydration:
<!-- src/routes/layout.koze -->
<script>
import ThemeInit from '@kuratchi/kuzan/theme-init.koze';
</script>
<!doctype html>
<html lang="en" class="dark">
<head>
<ThemeInit />
</head>
<body>
<slot></slot>
</body>
</html>
Then import components freely:
<script>
import Card from '@kuratchi/kuzan/card.koze';
import Badge from '@kuratchi/kuzan/badge.koze';
</script>
<Card>
<Badge variant="success">Active</Badge>
<p>Card content</p>
</Card>
Migrating from kuratchi.config.ts
Earlier versions of Koze had a project-level kuratchi.config.ts. It was deleted. Each block has a clean home in the new model:
| Old config block | New home |
|---|---|
orm.databases |
autoMigrate({ DB: schema }) step in src/middleware.ts |
auth.* |
kyzenAuthMiddleware({...}) step in src/middleware.ts |
ui.* |
import 'kuzan/styles/theme.css' in src/app.css + <ThemeInit /> in your layout |
css.* |
Standard PostCSS / Tailwind / Vite CSS plugins in src/app.css |
security.* |
koze({ security: {...} }) Vite plugin option |
durableObjects |
Auto-discovered from *.do.ts files. Filename derives the binding name (auth.do.ts → AUTH_DO) or override via static binding = '...' on the class. |
containers / workflows / queues / pipelines / sandboxes |
Auto-discovered from singular *.container.ts / *.workflow.ts / *.queue.ts / *.pipeline.ts / *.sandbox.ts files |
agents |
Place classes in singular *.agent.ts files; Koze re-exports them and you keep the matching Durable Object binding/migration in wrangler.jsonc |
Migration steps for an existing project:
- Delete
kuratchi.config.ts. - Move the
auth:block intosrc/middleware.tsviakyzenAuthMiddleware(authConfig). - Add
migrate: autoMigrate({ DB: yourSchema })tosrc/middleware.ts. - If you used
ui.theme, add@import '@kuratchi/kuzan/styles/theme.css'tosrc/app.cssand<ThemeInit />to your layout's<head>. - If you had
security:headers, move them tokoze({ security: {...} })invite.config.ts. - Remove the
kuratchi.config.tsline fromtsconfig.json'sincludearray.
That's the whole migration. The framework is a smaller mental model now — three honest config surfaces instead of one config file pretending to own everything.