# Kanel generates TypeScript types by reading a live Postgres schema

> Kanel is an MIT licensed TypeScript tool that connects to a running Postgres database and writes TypeScript interfaces for its tables, aimed at people who dislike ORMs but want intellisense and type checking on their database access code. It reads the live schema rather than a migration history, and it has become a monorepo with separate packages for Knex, Zod, Kysely and enum tables.

**kristiandupont/kanel** — Generate Typescript types from Postgres

- Repository: https://github.com/kristiandupont/kanel
- Website: https://kristiandupont.github.io/kanel/
- Stars: 1,183 · Forks: 78
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/kristiandupont-kanel

## The design targets people who do not want an ORM

Kanel generates TypeScript types from a live Postgres database, and the README states who that is for in one sentence. This is for people who don't like ORMs but who do like intellisense and type checking for their database access code.

That sentence defines both the opportunity and the boundary. The complaint about ORMs is usually not about the query language, it is about the abstraction cost, the lazy loading surprises, the difficulty of seeing the actual SQL, and the migration between a query builder and raw SQL later. A developer who wants to write SQL and still get a typed result object has to write the types by hand, and hand written types drift from the schema the moment someone adds a column.

Kanel closes that gap by reading the database rather than a description of it. The usage section says to make sure you are in a folder that has a kanel.config.js configuration file and that your database is running, and then to type npx kanel.

```bash
$ npm i -D kanel
$ npx kanel
```

The requirement that the database is running is the key design decision and the main trade. A generator that reads a live database cannot be wrong about the schema, because the schema is what it read. A generator that reads migration files can be wrong, and will be wrong in the way that matters most, quietly, after a migration that was applied by hand or by a tool that does not update the files the generator looks at.

The cost is that generation becomes a step that needs a database, which means it belongs in a development workflow or a CI job with a database service rather than in a build that runs with no infrastructure. For a type safety tool, that is a reasonable trade.

The project is MIT licensed, authored by Kristian Dupont, and the documentation is at kristiandupont.github.io/kanel with the idea outlined in a Medium article linked from the README.

## It is callable as a function, not only as a command

The usage section shows the command line path, but the second half of the README shows something that matters for a tool that has to run in CI.

```typescript
import { processDatabase } from "kanel";
import config from "./kanel.config.js";

async function run() {
  await processDatabase(config);
}

run();
```

So the core operation is an exported async function that takes a configuration object and returns a promise. The CLI is a thin wrapper over it. That is what makes the tool composable into a build script, a migration script that regenerates types right after a schema change, or a test that asserts the checked in types match the database.

The configuration is a JavaScript file, kanel.config.js, rather than JSON, and the import in the example is a default import of that file. A JavaScript configuration can contain logic, and for a tool that has to decide things like which tables to include, how to map Postgres types to TypeScript ones, and what to name the output, a config that can compute those values is more useful than one that can only list them.

The example folder in the repository shows the range of outputs the project produces. There is examples/generate-examples.js, which is the script that regenerates them, and three output directories with different shapes, examples/kysely/, examples/markdown/ and examples/ts-plus-zod/. The example database is the Sample Database from postgresqltutorial.com, and the scripts in the root package.json start it in Docker.

```json
"start-example-db": "docker start dvd-rental || docker run -d --name dvd-rental -p 54321:5432 kristiandupont/dvdrental-image"
```

So the examples are built against a real running Postgres, not a fixture, which is the only way the output can be trusted to reflect what the tool actually produces.

## A monorepo that grew into a family of generators

The root package.json shows that Kanel is no longer one package, and the shape of the workspace says something about where the project is going.

```json
"workspaces": [
  "packages/kanel",
  "packages/kanel-seeder",
  "packages/kanel-knex",
  "packages/kanel-zod",
  "packages/kanel-kysely",
  "packages/kanel-enum-tables"
]
```

The core package is kanel, and then there are five satellites. kanel-knex generates types for the Knex query builder, kanel-zod generates Zod schemas, kanel-kysely generates types for Kysely, kanel-enum-tables generates a TypeScript enum from a Postgres enum type, and kanel-seeder generates seed data.

The progression is a story about how people consume the output. The original was plain interfaces. Then people wanted a runtime validator, so Zod schemas. Then people wanted the types to match a query builder's expected shape, so Knex and Kysely variants. Then people wanted to seed a database, so the seeder. And Postgres enum columns are the kind of thing that is painful to represent in TypeScript, so that got its own package.

The seeder in particular is a different kind of tool from the others. Generating types is deterministic from the schema, but generating data means making choices about what plausible values look like for each column, and a foreign key has to point at an existing row. That is a generator with opinions, and opinions in a data generator are a source of surprising fixtures.

The documentation is built with VitePress from a docs-src/ directory, and the scripts are docs:dev and docs:build. The monorepo also has an AGENTS.md, a PLAN.md, a renovate.json for dependency updates, and an eslint.config.mjs, so the lint config uses the newer flat-config format.

## The release tags stopped in 2023 while the branch kept moving

The version history needs to be read carefully, because it is the main risk in adopting this project.

The tagged releases are v3.4.0 on 2023-06-18, v3.4.1 on 2023-07-11 and v3.5.1 on 2023-08-19. The last push to main was on 2026-10-15, more than three years after the most recent tag.

So the master branch has moved substantially since the last published release, and the package on npm is the older artefact. The root package.json version reads 0.0.0, which is the private monorepo root and is not the version of the published kanel package, so the number to look for is the one in packages/kanel.

The gap matters for a code generator, more than it would for a library. A type generator's output changes when the tool changes, and a team that regenerates types and finds a diff has to decide whether the change is a fix or a regression. A three year gap between the last tag and the branch means a team cannot easily tell which behaviour they are running, and it means the documentation on the site, which is built from the branch, may describe behaviour that the published package does not have.

The other thing to check is the node requirement, which is node >=16.0.0, and the dependency versions in the toolchain, with @eslint/compat at ^2.0.0, vitepress at ^1.6.3 and vue at 3.5.39, so the tooling is current even though the last release tag is not.

There is a third signal in the repository, which is the renovate.json. Automated dependency updates are the reason a branch can keep moving for years without a release, since dependabot style updates land on the branch continuously and the maintainer may not cut a release for each one.

## Where Kanel is the wrong choice

The first wrong case is a project that already generates its types. If you use an ORM that produces models, or you already have a schema-first codegen step, Kanel is a second source of truth. Two generators over one database means two artefacts to review, and the disagreement between them is a question nobody asked.

The second wrong case is a team that wants hand written types. Some codebases deliberately keep their domain types separate from their database types, mapping between them deliberately so that a schema change does not ripple into the domain layer. A generator that produces a one to one mirror of the tables works against that, because it removes the seam where the mapping was supposed to live.

The third wrong case is a build that has no database. Generating types needs a running Postgres, and that is fine in a development workflow or a CI job with a service container, but it is a problem in a build that runs in a sandbox, on a developer's laptop with no database running, or in a deployment pipeline where the database is deliberately not reachable. The generated types have to be checked in for that to work, which then reintroduces the drift problem that the tool was solving.

The fourth wrong case is expecting a mature package. The version history stops in 2023, the package is a pre-1.0 in practice in terms of what has been published since, and a code generator that is quietly changing its output is harder to depend on than one that is stable and old.

## How it differs from an ORM's model generation

The comparison worth making is with an ORM such as Prisma, TypeORM or Drizzle, and the difference in approach is what the generated artefact is for.

An ORM generates models that are part of the ORM's own runtime. Prisma's generated client is not merely types, it is a query engine with a schema description baked in, and the generated types are tightly coupled to that engine. Using the ORM means using the generated model as the only way to touch the data, and the abstraction you were avoiding comes back with the query builder attached.

Kanel generates interfaces and nothing else. The output describes the shape of a row, and how you get that row is entirely your decision, raw SQL with a tagged template, Knex, Kysely, or anything else. The package name is the point, it is Kanel rather than an ORM, and the additional kanel-knex and kanel-kysely packages exist precisely so that the types match the query builder you already chose without adopting the ORM.

The second difference is the source of truth. An ORM generally wants to own the schema, through migrations it generates, so that the database and the models cannot disagree. Kanel goes the other way, it reads the database that is already there. That is the right choice for a team that inherited a database, or that manages schema with a different tool, or that has a DBA in the loop. It is the wrong choice for a greenfield team that wants the schema, the types and the migrations all generated from one description.

The third difference is the runtime validation story. An ORM gives you a runtime layer that can enforce the schema. Kanel gives you types that the compiler checks and, with the kanel-zod package, a runtime validator, but the validator is a separate artefact you opt into rather than something that comes with the type generation.

## Conclusion

Kanel fits a team that keeps its schema in Postgres, wants plain SQL or a thin query builder rather than an ORM, and wants the TypeScript types to come from the database that is actually running rather than from a migration folder that has drifted. It does not fit a team whose types are hand written, or one that uses an ORM that already generates models, and it does not fit a project where the database is not reachable at build time. Before adopting it, decide which of the output packages you want, since the monorepo now ships kanel, kanel-seeder, kanel-knex, kanel-zod, kanel-kysely and kanel-enum-tables, and note that the newest tagged release is v3.5.1 from 2023-08-19 while the last push to main was on 2026-10-15.

## FAQ

### How do I install and run Kanel?

Install it as a dev dependency with npm i -D kanel, put a kanel.config.js configuration file in the folder you run it from, make sure your Postgres database is running, and then run npx kanel. The engine requirement is node >=16.0.0.

### Can Kanel be called programmatically rather than from the command line?

Yes. The package exports processDatabase, an async function that takes the configuration object, so you can import it and call it from a build or migration script. The example imports the config from ./kanel.config.js and awaits processDatabase(config).

### What packages does the Kanel monorepo contain?

The workspace lists packages/kanel, packages/kanel-seeder, packages/kanel-knex, packages/kanel-zod, packages/kanel-kysely and packages/kanel-enum-tables, so the core generator plus variants for Knex, Zod, Kysely, Postgres enum tables and seed data.

### How do I run the Kanel examples?

The root package.json has a start-example-db script that starts a Docker container running the DVD rental image on port 54321, a stop-example-db script, and a generate-examples script that runs examples/generate-examples.js. The example database is the Sample Database from postgresqltutorial.com.

### What is the latest Kanel release?

The tagged releases are v3.4.0 on 2023-06-18, v3.4.1 on 2023-07-11 and v3.5.1 on 2023-08-19, while the last push to main was on 2026-10-15. The project is MIT licensed and its documentation is at kristiandupont.github.io/kanel.

## Sources

- [kristiandupont/kanel on GitHub](https://github.com/kristiandupont/kanel)
- [License: MIT](https://github.com/kristiandupont/kanel/blob/main/LICENSE)
- [Project website](https://kristiandupont.github.io/kanel/)
- [README](https://github.com/kristiandupont/kanel/blob/main/README.md)
- [Releases](https://github.com/kristiandupont/kanel/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kristiandupont-kanel
