Configuration | Koze | Primitives Docs

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:

  1. Install: npm install @kuratchi/kuzan.
  2. Import the theme stylesheet from src/app.css (above).
  3. 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.tsAUTH_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:

  1. Delete kuratchi.config.ts.
  2. Move the auth: block into src/middleware.ts via kyzenAuthMiddleware(authConfig).
  3. Add migrate: autoMigrate({ DB: yourSchema }) to src/middleware.ts.
  4. If you used ui.theme, add @import '@kuratchi/kuzan/styles/theme.css' to src/app.css and <ThemeInit /> to your layout's <head>.
  5. If you had security: headers, move them to koze({ security: {...} }) in vite.config.ts.
  6. Remove the kuratchi.config.ts line from tsconfig.json's include array.

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.