Drizzle ORMPostgreSQLError Fix2026 Updated

Drizzle ORM: Not Null Constraint Violation— Fixed [2026]

Also covers: .notNull() · .default() · .defaultNow() · $defaultFn · nullable columns · drizzle-kit push

January 20266 min readDrizzle ORM · PostgreSQL · TypeScript

⚡ The Error

Error
PostgresError: null value in column "created_at" of relation "posts"
  violates not-null constraint
// OR:
PostgresError: null value in column "user_id" violates not-null constraint

✅ Fix — add .notNull().default() or provide value in insert

schema.ts
// Add .defaultNow() for timestamp columns
createdAt: timestamp("created_at").notNull().defaultNow(),

// OR provide the value explicitly in every insert
await db.insert(posts).values({ title, userId, createdAt: new Date() })

What Causes Not Null Constraint Violations in Drizzle

When you define a column with .notNull() in Drizzle, the database enforces that every inserted row must have a non-null value for that column. The error fires when an INSERT or UPDATE omits the column and there is no database-level DEFAULT to fall back to.

1

Add .notNull() and .default() to Schema Columns

Core schema pattern for required fields with defaults
3 min
db/schema.ts — column definition patterns
import { pgTable, text, timestamp, uuid, boolean, integer } from "drizzle-orm/pg-core"
import { createId } from "@paralleldrive/cuid2"

export const posts = pgTable("posts", {
  // Primary key — auto-generated
  id: uuid("id").primaryKey().defaultRandom(),

  // Required fields — no default — must provide in every insert
  title:  text("title").notNull(),
  userId: uuid("user_id").notNull().references(() => users.id),

  // Optional field — nullable (no .notNull())
  excerpt: text("excerpt"),

  // Boolean with default
  published: boolean("published").notNull().default(false),

  // Integer with default
  viewCount: integer("view_count").notNull().default(0),

  // Timestamps — auto-set by database
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
})
2

Provide All Required Fields in Insert Payload

Not null column with no default — must supply value
2 min
Correct insert — provide all notNull columns
// ❌ Missing required userId — violates not null
await db.insert(posts).values({ title: "Hello World" })

// ✅ Provide all required fields
await db.insert(posts).values({
  title: "Hello World",
  userId: user.id,           // required — no default
  published: false,          // optional — has default(false), but explicit is fine
  // createdAt omitted — database fills defaultNow()
})

// ✅ Returning — get the inserted row back
const [newPost] = await db
  .insert(posts)
  .values({ title: "Hello", userId: user.id })
  .returning()

console.log(newPost.id, newPost.createdAt)   // auto-filled by DB
3

Use .defaultNow() for Timestamp Columns

createdAt / updatedAt always null on insert
2 min
Timestamp column patterns
import { timestamp } from "drizzle-orm/pg-core"

export const posts = pgTable("posts", {
  // createdAt — set once at insert time
  createdAt: timestamp("created_at", { withTimezone: true })
    .notNull()
    .defaultNow(),

  // updatedAt — update on every change using Drizzle's hook
  updatedAt: timestamp("updated_at", { withTimezone: true })
    .notNull()
    .defaultNow()
    .$onUpdate(() => new Date()),
    // $onUpdate fires on every UPDATE query automatically
})

// After running drizzle-kit push, both columns are set automatically:
await db.insert(posts).values({ title: "Hello", userId: user.id })
// createdAt and updatedAt are set by DB + Drizzle — no need to pass them
4

Use JS-Level Defaults with $defaultFn

Generated IDs, slugs, random values
2 min
$defaultFn — compute default in JavaScript
import { pgTable, text, varchar } from "drizzle-orm/pg-core"
import { createId } from "@paralleldrive/cuid2"
import slugify from "slugify"

export const posts = pgTable("posts", {
  // CUID2 id — generated in JS, not SQL
  id: varchar("id", { length: 128 })
    .primaryKey()
    .$defaultFn(() => createId()),

  // Slug generated from title at insert time
  slug: text("slug")
    .notNull()
    .$defaultFn(() => slugify(Math.random().toString())),
    // Better: compute slug from title before inserting
})

// Usage — id and slug are computed by Drizzle before INSERT
const [post] = await db.insert(posts).values({ title: "Hello", userId: user.id }).returning()
console.log(post.id)    // "clxxxxxxxxxxxxxx" — cuid2
console.log(post.slug)  // generated slug

Use $defaultFn for IDs and computed values that need JavaScript logic. Use .default() for simple static defaults (false, 0, 'draft'). Use .defaultNow() specifically for timestamp columns.

5

Sync Schema Changes with drizzle-kit push

Schema updated but database still has old column definition
3 min

After changing your schema file, you must push the changes to the database. Without this, the database still enforces the old column constraints.

terminal — apply schema changes
# Push schema directly to DB (development — no migration files)
npx drizzle-kit push

# OR generate migration files (production-safe)
npx drizzle-kit generate
npx drizzle-kit migrate

# Check what drizzle-kit will change before applying
npx drizzle-kit push --dry-run

# Verify current schema vs DB
npx drizzle-kit check

Always run drizzle-kit push (or generate + migrate) after changing your schema. Drizzle's TypeScript schema and the actual database schema can drift — this is the #1 cause of unexpected constraint errors in development.

Prevention

Frequently Asked Questions

What does 'not null constraint violation' mean in Drizzle ORM?+
It means you tried to insert or update a row while leaving a column that has a NOT NULL constraint with no value and no default. The database rejects the operation. The fix is to either provide a value for the column in your insert, or define a default value in the schema.
How do I make a column required in Drizzle ORM?+
In Drizzle, columns are nullable by default unless you call .notNull() on them. Add .notNull() to any column that must always have a value. Combine with .default(value) to provide a fallback when the column is not explicitly set in an insert.
What is the difference between .default() and .$defaultFn() in Drizzle?+
default(value) sets a SQL-level DEFAULT — the database fills it in if the column is omitted. $defaultFn(() => value) sets a JavaScript-level default that Drizzle applies before sending the INSERT to the database. Use $defaultFn for values like nanoid(), crypto.randomUUID(), or any JS computation.
How do I auto-set createdAt in Drizzle ORM?+
Use .defaultNow() on a timestamp column: createdAt: timestamp('created_at').notNull().defaultNow(). The database fills in the current timestamp automatically. For updatedAt that updates on every change, use .$onUpdateFn(() => new Date()) which Drizzle applies on every UPDATE.
Why does my Drizzle schema look correct but I still get not null errors?+
The most likely cause is that your database schema is out of sync with your Drizzle schema file. Run npx drizzle-kit push (or generate + migrate) to apply your schema changes to the database. Without this, the database still has the old column definition.
Can I have a column that is not null but has no default?+
Yes — this is the standard pattern for required fields like name, email, user_id. The column has .notNull() but no .default(). Every insert must explicitly provide a value for this column, or the database rejects it with a not null constraint error.

Need Expert Help?

We Build Production Apps with Drizzle ORM

Softplix engineers design Drizzle schemas, write migrations, and build type-safe database layers for Next.js and Node.js. Let us help.

Talk to an Engineer