---
title: Codegen Plugin
description: Generate TypeScript row types, optional Zod schemas, ingestion functions, and runtime migration modules from chkit schema definitions.
sidebar:
  order: 2
---

import { Tabs, TabItem } from '@astrojs/starlight/components';

This document covers practical usage of the optional `codegen` plugin. In TypeScript it emits row types (plus optional Zod schemas, ingest helpers, and a runtime migration module); in Python it emits Pydantic models for every table and dictionary — Pydantic covers both static typing and runtime validation, so there is no separate Zod-style output.

## What it does

- Generates deterministic row types from chkit schema definitions — TypeScript interfaces, or Pydantic models in Python.
- Generates separate read and insert types for [tables with generated columns](#tables-with-generated-columns).
- Generates a typed interface (and optional Zod schema) for each `dictionary()` from its `attributes` — dictionaries are always included, regardless of `includeViews`.
- Optionally generates Zod schemas from the same definitions.
- Optionally generates typed ingestion functions for inserting rows into ClickHouse tables. Generated ingest helpers gzip-compress request bodies by default and can opt out per call.
- Optionally generates a self-contained runtime migration module with all migration SQL inlined, for environments without filesystem access (e.g., Cloudflare Workers).

## How it fits your workflow

The plugin is designed so your existing chkit workflow can stay the same.

- [`chkit generate`](/cli/generate/) integration:
  - After a successful migration/snapshot generate, codegen runs automatically by default (`runOnGenerate: true`).
  - Result: migration artifacts and generated types stay in sync in normal dev flow.
- [`chkit check`](/cli/check/) integration:
  - `check` evaluates codegen freshness via plugin hook.
  - If generated types are missing/stale, `failedChecks` includes `plugin:codegen`.
  - Result: CI enforcement works without adding a separate codegen step.
- [`chkit codegen`](/cli/codegen/) command:
  - Optional manual trigger.
  - Useful when you explicitly want to regenerate or run an isolated `--check`.

## Plugin setup

Register `codegen(...)` in your config's `plugins` array.

<Tabs syncKey="lang">
  <TabItem label="TypeScript">
    :::note
    `zod` is a peer dependency (`^4.0.0`). Install it alongside the plugin — generated Zod schemas (`emitZod: true`) import `zod` from your project, so they resolve against your own copy rather than a bundled one.

    ```sh
    bun add -d @chkit/plugin-codegen zod
    ```
    :::

    Recommended typed setup:

    ```ts
    import { defineConfig } from '@chkit/core'
    import { codegen } from '@chkit/plugin-codegen'

    export default defineConfig({
      schema: './src/db/schema/**/*.ts',
      plugins: [
        codegen({
          outFile: './src/generated/chkit-types.ts',
          emitZod: false,
          emitIngest: false,
          ingestOutFile: './src/generated/chkit-ingest.ts',
          emitMigrations: false,
          migrationsOutFile: './src/generated/chkit-migrations.ts',
          tableNameStyle: 'pascal',
          bigintMode: 'string',
          includeViews: false,
          runOnGenerate: true,
          failOnUnsupportedType: true,
        }),
      ],
    })
    ```
  </TabItem>
  <TabItem label="Python">
    The plugin ships inside `chkit-py` — nothing extra to install.

    ```python
    from chkit import define_config
    from chkit_plugin_codegen import codegen

    config = define_config(
        {
            "schema": "./src/db/schema/**/*.py",
            "plugins": [
                codegen(
                    {
                        "outFile": "./src/generated/chkit_models.py",
                        "tableNameStyle": "pascal",
                        "bigintMode": "int",
                        "includeViews": False,
                        "runOnGenerate": True,
                        "failOnUnsupportedType": True,
                    }
                ),
            ],
        }
    )
    ```

    The output is a single module with Pydantic models for every table and dictionary. The `emitZod` / `emitIngest` / `emitMigrations` emitters are TypeScript-only by design — Pydantic already provides runtime validation, and the ingest/migration-module emitters target JS runtimes.
  </TabItem>
</Tabs>

## Options

- `outFile` (default: `./src/generated/chkit-types.ts`)
- `emitZod` (default: `false`)
- `emitIngest` (default: `false`)
- `ingestOutFile` (default: `./src/generated/chkit-ingest.ts`)
- `emitMigrations` (default: `false`)
- `migrationsOutFile` (default: `./src/generated/chkit-migrations.ts`)
- `tableNameStyle` (default: `pascal`) values: `pascal | camel | raw`
- `bigintMode` (default: `string`) values: `string | bigint` — in Python the values are `int | str` (default `int`; the TS spellings are accepted as aliases)
- `includeViews` (default: `false`)
- `runOnGenerate` (default: `true`)
- `failOnUnsupportedType` (default: `true`)

Python supports `outFile` (default `./src/generated/chkit_models.py`), `tableNameStyle`, `bigintMode`, `includeViews`, `runOnGenerate`, and `failOnUnsupportedType`; the `emit*` options are TypeScript-only (see Plugin setup above).

Invalid option values fail fast at startup via plugin config validation.

## Commands

- [`chkit codegen`](/cli/codegen/)
  - Optional manual command to generate and write output atomically.
- [`chkit codegen --check`](/cli/codegen/)
  - Optional manual check to validate output is current without writing.
  - Fails with:
    - `codegen_missing_output` (types file missing)
    - `codegen_stale_output` (types content drift)
    - `codegen_missing_ingest_output` (ingest file missing, when `emitIngest` is enabled)
    - `codegen_stale_ingest_output` (ingest content drift, when `emitIngest` is enabled)
    - `codegen_missing_migrations_output` (migrations file missing, when `emitMigrations` is enabled)
    - `codegen_stale_migrations_output` (migrations content drift, when `emitMigrations` is enabled)

Useful flags:

- `--out-file <path>`
- `--emit-zod` / `--no-emit-zod`
- `--emit-ingest` / `--no-emit-ingest`
- `--ingest-out-file <path>`
- `--emit-migrations` / `--no-emit-migrations`
- `--migrations-out-file <path>`
- `--bigint-mode <string|bigint>`
- `--include-views`

## Tables with generated columns

A table with [`MATERIALIZED`, `ALIAS`, or `EPHEMERAL`](/schema/dsl-reference/#defaultkind-optional) columns gets three types instead of one, plus matching Zod schemas with `emitZod`. Python emits the same three as Pydantic models.

| Type | Columns | Use for |
|------|---------|---------|
| `Row` | Ordinary and `DEFAULT` columns | `SELECT *` results |
| `RowExplicit` | Every column except `EPHEMERAL` | Queries that name `MATERIALIZED` or `ALIAS` columns |
| `RowInsert` | Every column except `MATERIALIZED` and `ALIAS` | Inserts, including `EPHEMERAL` inputs |

The names extend the table's type name, for example `DefaultEventsRowInsert`. `Row` assumes the default `SELECT *`; with `asterisk_include_materialized_columns` or `asterisk_include_alias_columns`, name the columns and use `RowExplicit`.

## Generated ingest helpers

When `emitIngest` is enabled, generated ingest helpers accept runtime options. Request-body compression is enabled by default for these helpers:

```ts
await ingestDefaultUsers(db, rows)
```

Disable compression for a specific call when needed:

```ts
await ingestDefaultUsers(db, rows, { compressed: false })
```

When `emitZod` is enabled, the same options object also controls validation:

```ts
await ingestDefaultUsers(db, rows, { validate: true })
```

### Ingesting into tables with generated columns

Ingest helpers take `RowInsert[]` for these tables. ClickHouse drops `EPHEMERAL` values unless the `INSERT` names its columns, so for a table with `EPHEMERAL` columns the helper passes `columns` to `ingestor.insert`, and the generated `Ingestor` interface gains `columns?: string[]`. The `@chkit/clickhouse` executor sends it as the `INSERT` column list; a custom `Ingestor` must do the same (`@clickhouse/client` takes it as the `columns` option of `insert()`). The list comes from the schema, so apply pending migrations before ingesting.

## CI / check integration

When configured, [`chkit check`](/cli/check/) includes a `plugins.codegen` block in JSON output and can fail with `plugin:codegen`.

`plugin:codegen` is added to `failedChecks` when the plugin check returns an error finding (for example stale or missing generated artifacts).

## Generate integration

When `runOnGenerate` is enabled (default), [`chkit generate`](/cli/generate/) runs codegen after successful migration/snapshot generation.

If codegen fails in that path, `chkit generate` fails.

## Runtime migration module

When `emitMigrations` is enabled, the plugin generates a self-contained TypeScript module that inlines all migration SQL. This is designed for environments without filesystem access at runtime — such as Cloudflare Workers, Deno Deploy, or serverless functions — where reading `.sql` files from disk is not possible.

The generated file exports:

- **`migrations`** — an array of `MigrationEntry` objects, each containing a `name` and the raw `sql` string.
- **`runMigrations(executor, options?)`** — applies pending migrations against a provided executor, tracking progress in a `_chkit_migrations` journal table.

### MigrationExecutor interface

The `runMigrations` function accepts any object that satisfies the `MigrationExecutor` interface:

```ts
interface MigrationExecutor {
  execute(sql: string): Promise<void>
  query<T>(sql: string): Promise<T[]>
}
```

This keeps the generated module independent of any specific ClickHouse client library. Wrap your client to match this interface.

### Usage example

```ts
import { runMigrations } from './generated/chkit-migrations'

const result = await runMigrations(executor)
// result.applied  — migrations that were run
// result.skipped  — migrations already in the journal
```

You can override the journal table name:

```ts
await runMigrations(executor, { journalTable: 'my_migrations' })
```

### How it stays in sync

When `runOnGenerate` is enabled (the default), the migration module is regenerated every time `chkit generate` produces new migrations. `chkit check` and `chkit codegen --check` detect stale or missing migration output via the `codegen_missing_migrations_output` and `codegen_stale_migrations_output` error codes.

## Current limits

- Query-level type inference is not included.
- Arbitrary SQL expression typing is not included.
- Views/materialized views are opt-in (`includeViews`) and emitted conservatively (`{ [key: string]: unknown }`). Dictionaries are always included and typed from `attributes`, since their shape is structured rather than an arbitrary query.
