Casing moves into your schema

In TypeScript, you name properties in camelCase: fullName, createdAt. Many databases name columns in snake_case: full_name, created_at. Casing is the rule that converts one into the other, so you don’t write every column name twice.

In 0.x (legacy), you set this rule twice: once in drizzle() for your queries, and once in drizzle.config.ts for drizzle-kit. v1 removes both. You now set it once, on the table, and both your queries and drizzle-kit use it.

Diff layout

Before · Legacy 0.x

After · rc4

// In db.ts
const db = drizzle({
client,
casing: "snake_case",
});
// In schema.ts
export const users = pgTable("users", {
id: serial().primaryKey(),
fullName: text().notNull(),
createdAt: timestamp().defaultNow().notNull(),
});
// In db.ts
const db = drizzle({ client });
// In schema.ts
export const users = snakeCase.table("users", {
id: serial().primaryKey(),
fullName: text().notNull(),
createdAt: timestamp().defaultNow().notNull(),
});

Your columns keep the same names: fullName is still stored as full_name, and createdAt as created_at. Delete casing from drizzle.config.ts as well. drizzle-kit v1 doesn’t read it anymore.

If you wrote every column name by hand, like text("full_name"), casing never changed anything for you. You only need to delete the casing option, because TypeScript now rejects it.

introspect.casing in drizzle.config.ts is a different option. It only controls how drizzle-kit pull names properties, and it still exists.

Don’t just delete casing

Without casing, Drizzle looks for a column called fullName. Your database has full_name. Two things break.

Your queries fail. A select, for example, stops with column "fullName" does not exist.

Your next generate thinks you changed the table. It asks you about each column:

Is users.fullName column created or renamed from another column?

In CI, where nobody can answer, it stops with missing_hints and exit code 2.

You can only answer “renamed” or “created”, and both change your database:

  • Renamed: drizzle-kit renames full_name to fullName in your database. Your data stays, but the column names change, so any other app, script or SQL query that still uses full_name breaks.
  • Created: the migration adds a new, empty fullName column and deletes full_name, with its data.

You want neither. Use snakeCase.table instead, and generate says No schema changes, nothing to migrate.

Leaving casing in drizzle() doesn’t save you either. TypeScript rejects it, and in a plain JavaScript file, where nothing checks types, v1 silently ignores it. Your queries fail the same way.

Each dialect has its own casing helpers, imported from its -core package. In drizzle-orm/pg-core, you can apply casing to a table, a view, a materialized view, or a whole schema:

// Every table in this schema gets snake_case columns
export const app = snakeCase.schema("app");
export const teams = app.table("teams", {
id: serial().primaryKey(),
displayName: text(), // display_name
});

camelCase.table works the other way: it names columns in camelCase, so a display_name key is stored as displayName.

Generated columns only take SQL

A generated column is a column the database computes from other columns. In 0.x, you could pass .generatedAlwaysAs() a plain string. v1 only accepts an sql template, or a function that returns one, so a string is now a type error.

Diff layout

Before · Legacy 0.x

After · rc4

export const users = pgTable("users", {
id: serial().primaryKey(),
fullName: text().notNull(),
searchName: text().generatedAlwaysAs("lower(full_name)"),
});
export const users = snakeCase.table("users", {
id: serial().primaryKey(),
fullName: text().notNull(),
searchName: text().generatedAlwaysAs(sql`lower(full_name)`),
});

Postgres: multi-dimensional arrays

An array column stores a list of values, like integer[]. A two-dimensional array, integer[][], stores a list of lists, such as a grid of scores. In v1, you give the number of dimensions in one call:

Diff layout

Before · Legacy 0.x

After · rc4

export const boards = pgTable("boards", {
id: serial().primaryKey(),
grid: integer().array().array(),
});
export const boards = pgTable("boards", {
id: serial().primaryKey(),
grid: integer().array("[][]"),
});

Postgres: row-level security

Row-level security (RLS) lets Postgres decide which rows each user can read or change. .enableRLS() still works in rc4, but it’s deprecated. Turn RLS on when you declare the table instead:

Diff layout

Before · Legacy 0.x

After · rc4

export const posts = pgTable("posts", {
id: serial().primaryKey(),
}).enableRLS();
export const posts = pgTable.withRLS("posts", {
id: serial().primaryKey(),
});

Validators move into drizzle-orm

Validator packages turn your tables into validation schemas, for example to check a request body before you insert it. In v1, they’re part of drizzle-orm:

0.x package v1 import
drizzle-zod drizzle-orm/zod
drizzle-valibot drizzle-orm/valibot
drizzle-typebox (@sinclair/typebox) drizzle-orm/typebox-legacy
none (typebox 1.x) drizzle-orm/typebox (new)
drizzle-arktype drizzle-orm/arktype
none drizzle-orm/effect-schema (new)

The functions have the same names, so usually you only change the import. You still install the validation library itself, such as zod:

import { createInsertSchema } from "drizzle-orm/zod";
const userInsertSchema = createInsertSchema(users);
userInsertSchema.parse({ fullName: "Ada Lovelace" });

The docs say you can keep the old packages, but new features only go into drizzle-orm. Keeping them isn’t smooth anyway: npm refuses to install them next to drizzle-orm@1.0.0-rc.4 unless you pass --legacy-peer-deps.

getColumns replaces getTableColumns

getTableColumns still works in rc4, but it’s deprecated. Rename it:

const { fullName, ...rest } = getColumns(users);

Check your work

Once your schema compiles, run generate. None of the changes above should touch your database, so drizzle-kit should have nothing to do:

Package manager
npx drizzle-kit generate
pnpm drizzle-kit generate
yarn drizzle-kit generate
bunx drizzle-kit generate
No schema changes, nothing to migrate 😴

If it asks about columns, or wants to create, rename or drop anything, stop. Compare your schema with your database before you answer.

Further reading

How we checked: we generated migrations with drizzle-kit@0.31.10, converted them with drizzle-kit up, then ran generate again with 1.0.0-rc.4, before and after the schema changes in this article. The query errors come from running the queries against PGlite, an in-memory PostgreSQL.

The v0 to v1 changes (Drizzle docs, opens in a new tab) list the breaking changes for each dialect, and the upgrade guide (Drizzle docs, opens in a new tab) gives the order. For migrations and relations, see Migrations in v1 and Relational Queries v2.