PostgreSQL

Drizzle: Object literal may only specify known properties

Get .values() and .set() accepting your nullable Drizzle columns again: which versions lose them, why a loose tsconfig is the trigger, and the workaround that does not help.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

TL;DR#

From drizzle-orm 0.32.0 until 0.43.0, a project whose tsconfig.json leaves strictNullChecks off gets a wrong insert type: every column that is optional on insert (nullable columns, columns with a default, even a serial primary key) disappears from it. Passing one of those columns to .values() then fails with TS2769 and "Object literal may only specify known properties", and .set() on an update fails the same way. The fix is to upgrade to drizzle-orm 0.43.0 or later, whose release notes name this bug. If you cannot upgrade yet, set "strictNullChecks": true on its own; .notNull() is not a fix, because it changes what the column means.

The report is drizzle-orm issue #2619, opened when its author moved from drizzle-orm 0.31.4 to 0.32.0. On a MySQL table, the nullable isUsed and isActive columns were no longer accepted by .values():

text
src/services/sessions/index.ts:31:8 - error TS2769: No overload matches this call.

followed, for each overload, by:

text
Object literal may only specify known properties, and 'isUsed' does not exist in type '{ currency: string | SQL<unknown> | Placeholder<string, any>; platformType: number | SQL<unknown> | Placeholder<string, any>; sessionId: string | SQL<unknown> | Placeholder<...>; playerId: number | ... 1 more ... | Placeholder<...>; startTime: string | ... 1 more ... | Placeholder<...>; partnerTokenId: number | ... ...'.

The listed type is the giveaway: it only contains the columns declared .notNull() without a default. The issue was closed in December 2024 with a maintainer comment saying it should not happen on 0.38.0 or later, and commenters kept reporting it on 0.38.x afterwards. The reproduction below agrees with them.

Reproducing it on PostgreSQL#

The reproduction ran on 24 October 2026 in the official node:22-alpine Docker image with TypeScript 5.9.3, as a type-check only (no database is needed to get a type error). It uses pg-core rather than the MySQL table of the report; the insert-key types live in Drizzle's shared operations.ts, so the dialect does not matter. The table has two required columns, a serial key and two nullable columns:

ts
import { pgTable, serial, text, boolean, timestamp } from "drizzle-orm/pg-core";
 
export const authSessions = pgTable("auth_sessions", {
  id: serial("id").primaryKey(),
  sessionId: text("session_id").notNull(),
  playerId: text("player_id").notNull(),
  isUsed: boolean("is_used"),
  endedAt: timestamp("ended_at"),
});

The insert passes the nullable isUsed column:

ts
import { Pool } from "pg";
import { drizzle } from "drizzle-orm/node-postgres";
import { authSessions } from "./schema";
 
const db = drizzle(new Pool({ connectionString: process.env.DATABASE_URL }));
 
export async function openSession(playerId: string, sessionId: string) {
  await db.insert(authSessions).values({
    sessionId,
    playerId,
    isUsed: false,
  });
}

With drizzle-orm 0.42.0 and "strict": false, the compiler rejects it exactly as in the issue:

text
src/insert.ts(8,33): error TS2769: No overload matches this call.
    Object literal may only specify known properties, and 'isUsed' does not exist in type '{ sessionId: string | SQL<unknown> | Placeholder<string, any>; playerId: string | SQL<unknown> | Placeholder<string, any>; }'.

To see the whole damage at once, ask the compiler for the keys of the insert type by assigning a string that cannot match:

ts
import { authSessions } from "./schema";
 
type InsertKeys = keyof typeof authSessions.$inferInsert;
const probe: InsertKeys = "not-a-column";

The error message lists the keys. On 0.31.4 they are all there; on 0.32.0 only two are left:

text
### drizzle-orm drizzle-orm@0.31.4 strict:false src/keys.ts
src/keys.ts(4,7): error TS2322: Type '"not-a-column"' is not assignable to type '"id" | "sessionId" | "playerId" | "isUsed" | "endedAt"'.
### drizzle-orm drizzle-orm@0.32.0 strict:false src/keys.ts
src/keys.ts(4,7): error TS2322: Type '"not-a-column"' is not assignable to type '"sessionId" | "playerId"'.

The same probe returned only the two required keys on 0.32.1, 0.33.0, 0.34.1, 0.35.3, 0.36.4, 0.37.0, 0.38.0, 0.38.4, 0.40.0 and 0.42.0, and all five keys on 0.43.0, 0.44.7, 0.45.2 and 0.45.4. The full run is in the reproduction log.

Why a loose tsconfig breaks the insert type#

Drizzle builds the insert type from two helper types in operations.ts. A column is a required key when it is declared not null without a default; every other column should become an optional key. In 0.31.4, "every other column" was literally that: any key that was not required. 0.32.0 added checks for generated columns, so that a column computed by the database is not offered on insert. Those checks compare the column's metadata against undefined, and by 0.40.0 the optional-key type also asks whether the column's identity setting extends 'always'.

That comparison only works when TypeScript treats undefined as its own type. The TypeScript reference for strictNullChecks says:

When strictNullChecks is false, null and undefined are effectively ignored by the language.

A three-line probe shows what that means for a conditional type like the one in the 0.40.0 code. With strictNullChecks off, undefined extends "always" is true, so an ordinary column, whose identity setting is undefined, is treated like a generated always as identity column and removed from the insert type. The 0.32.0 form of the check is written differently and is not traced step by step here, but it fails the same way in the reproduction:

ts
type IdentityOfPlainColumn = undefined;
type IsAlways = IdentityOfPlainColumn extends "always" ? "treated as identity always" : "treated as plain column";
const result: IsAlways = "show me";
text
$ npx tsc -p tsconfig.json   # src/probe.ts, compilerOptions {"strict":false}
src/probe.ts(3,7): error TS2322: Type '"show me"' is not assignable to type '"treated as identity always"'.
$ npx tsc -p tsconfig.json   # src/probe.ts, compilerOptions {"strict":false,"strictNullChecks":true}
src/probe.ts(3,7): error TS2322: Type '"show me"' is not assignable to type '"treated as plain column"'.

The required keys survive because their test only reads the column's not-null and default flags, which are booleans. That is why the type in the error message still lists sessionId and playerId while every optional column, the serial key included, is missing. In 0.43.0 the optional-key type first checks whether identity is undefined and keeps the key in that case, and the 0.43.0 release notes list "Fixed incorrect type inferrence for insert and update models with non-strict tsconfigs". An earlier change with a similar title, merged to the beta branch in November 2024 as PR #3542, was already in 0.40.0, and 0.40.0 still drops the keys in the reproduction, so the release that matters is 0.43.0.

Fix#

  1. Check which drizzle-orm your project really resolves. A monorepo or an adapter package can pull its own copy:

    bash
    npm ls drizzle-orm
  2. Upgrade drizzle-orm to 0.43.0 or later. In the reproduction, the same insert compiled with "strict": false once the version changed and nothing else did:

    bash
    npm install drizzle-orm@^0.45.4
  3. If you have to stay on 0.32.0 to 0.42.x, turn on strictNullChecks alone and leave the rest of strict as it is. In the reproduction, this one option made both the failing insert and the failing .set() compile on 0.32.0, 0.38.4 and 0.42.0:

    json
    {
      "compilerOptions": {
        "strict": false,
        "strictNullChecks": true
      }
    }

    Expect new errors elsewhere: with the option on, TypeScript stops letting null and undefined pass for any other type, so code such as reading process.env or the result of .find() will need a check before use. Switch on the whole strict family instead and you also get strictPropertyInitialization, which the TypeScript reference describes as raising an error "when a class property was declared but not set in the constructor". In a NestJS or other class-based codebase, that next error is TS2564, and it has its own walkthrough.

    This workaround has one public counter-report: on issue #3797, a comment from January 2025 says the .set() error persisted with both strict and strictNullChecks enabled. The reproduction did not show that, so if it happens to you, the upgrade in step 2 is the way out.

Avoid two workarounds from the issue thread. Adding .notNull() to the column makes the error go away because the column becomes required, which changes your schema and breaks every insert that omits it. Casting the object to any turns type checking off for the whole call, including the columns that were checked correctly, and casting it to a Partial of the select type makes every column optional, so a missing required column is no longer caught.

Verify the fix#

Run the type-check on the file that failed:

bash
npx tsc --noEmit

With 0.43.0 installed, the reproduction's insert, update and annotated-object files all compiled under "strict": false:

text
==== drizzle-orm@0.43.0
$ npx tsc -p tsconfig.json   # src/insert.ts, compilerOptions {"strict":false}
exit=0
$ npx tsc -p tsconfig.json   # src/update.ts, compilerOptions {"strict":false}
exit=0

The keyof probe from the reproduction is the quickest confirmation on your own table: after the fix, its error message lists every column, nullable ones included.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Why typing the object as $inferInsert does not help on 0.32 to 0.42#

A workaround repeated several times in the thread is to build the row first with a type annotation and pass the variable to .values(). On an affected version it fails too, because $inferInsert is the same computed type. On 0.42.0 with "strict": false:

text
src/typed-object.ts(11,5): error TS2353: Object literal may only specify known properties, and 'isUsed' does not exist in type '{ sessionId: string; playerId: string; }'.

The error only moves from the .values() call to the variable declaration. In the reproduction, the annotated object compiled only on 0.43.0 and later, where the inline object compiles as well, so the annotation is good style but not a fix.

The same error from .set() on an update#

The 0.43.0 release note covers "insert and update models", and .set() on a nullable column fails at the same version boundary, with TS2353 instead of TS2769, as reported separately in issue #3797. On 0.42.0 with "strict": false, setting the nullable endedAt column gave:

text
src/update.ts(9,39): error TS2353: Object literal may only specify known properties, and 'endedAt' does not exist in type '{ sessionId?: string | SQL<unknown> | PgColumn<ColumnBaseConfig<ColumnDataType, string>, {}, {}>; playerId?: string | SQL<unknown> | PgColumn<...>; }'.

Both statements compiled on 0.43.0, so one upgrade fixes inserts and updates together.

Still failing on 0.43.0 or later#

Comments on the issue kept arriving into 2026 from projects on the 0.45 line, while the reproduction compiled cleanly on 0.44.7, 0.45.2 and 0.45.4. Before looking further, run npm ls drizzle-orm again: if it prints more than one version, find which package pulls the older copy and bring it to 0.43.0 or later too, then re-run the keyof probe. If the probe lists every column but .values() still rejects one, the cause is not this bug: compare the column name in your object with the key in your table definition, which is what the error message is checking.

Drizzle's types have broken in other ways across these releases too. Generic query helpers that pass a subquery or table to .from() fail with TS2345 and TableLikeHasEmptySelection, and relational db.query calls on a pgSchema table lose their schema name and hit relation does not exist on a few releases. If the editor rather than the compiler is the problem, slow IntelliSense on large Drizzle schemas has a different cause. For this error, the order is short: confirm the resolved version, upgrade past 0.42.x, and only reach for strictNullChecks when the upgrade has to wait.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote