Drizzle Insert Explained: How to Insert Data (With Examples)

Speed, accuracy, and simplicity — that’s exactly what Drizzle ORM brings to database insertion.

Whether you’re adding a single user from a signup form, inserting hundreds of records at once, returning newly created rows instantly, or preventing duplicates with smart upserts, Drizzle gives you a clean and type-safe way to handle it all.

This guide walks you through every practical use case of inserting data in Drizzle ORM, from basic inserts to bulk operations, conditional logic, and datatype handling — everything you need to build real-world apps without touching raw SQL.

Insert One

Imagine you just built a signup form. A user hits “Submit,” and you need to add that person to your database — fast and error-free.

That’s where Drizzle ORM’s Insert One feature shines. With just a few lines, you can insert a single row (record) into your table without writing complicated SQL.

Example: Inserting a Row (Record)

import { db } from "./db";
import { users } from "./schema";

const newUser = await db.insert(users).values({
    name: "Sharukhan",
    email: "sharukhan@example.com",
    age: 25
}).returning();

Insert Many

Sometimes, your app doesn’t just need to insert one record — it needs dozens or even hundreds at once. Drizzle ORM makes bulk insertion simple, safe, and fast.

Example: Inserting Multiple Rows (Values)

const newUsers = await db.insert(users).values([
    { name: "react", email: "react@frontend.com", age: 28 },
    { name: "zod", email: "zod@validation.com", age: 32 },
    { name: "kubernetes", email: "kubernetes@orchestration.com", age: 24 },
]).returning();

console.log("Inserted Records:", newUsers);

Key Points:

  • Multiple rows / values: Each object in the array represents a row or record.
  • Batch insert advantage: Saves time and reduces network overhead compared to inserting one row at a time.

Insert and Return

When you insert data into a database, it’s often helpful to get the inserted record immediately without writing a separate query.

Drizzle ORM’s .returning() makes this effortless. You can return IDs, values, objects, arrays, and even specific types in one step.

Example: Return the Inserted Record

const newUser = await db.insert(users).values({
  name: "Sharukhan",
  email: "sharukhan@example.com",
  age: 25
}).returning();

console.log("Inserted Record:", newUser);

Example: Returning Specific Fields Only

const newUser = await db.insert(users)
  .values({ name: "Khan", email: "khan@example.com", age: 28 })
  .returning({ id: true, name: true });

console.log(newUser);

Conditional Insertion with Upsert

Why manually check if a record exists when Drizzle ORM can handle it for you? With upsert, you can insert a new row if it doesn’t exist, or update the existing one in a single operation.

This is perfect for avoiding duplicates (conflicts) while keeping your data current. Maybe a user already exists, or an email has already been registered.

Drizzle ORM makes conditional insertion simple using checks like “if not exists” patterns.

Example: Upsert a User

const user = await db
  .insert(users)
  .values({
    name: "Sharukhan",
    email: "sharukhan@example.com",
    age: 25,
  })
  .onConflictDoUpdate({
    target: users.email,       // unique constraint column
    set: {
      name: "Sharukhan Updated", // fields to update if conflict occurs
      age: 26,
    },
  })
  .returning();

console.log("Upserted Record:", user);

Benefits of Upsert:

  1. Prevents Duplicates: No need for separate select checks.
  2. Atomic Operation: Safe from race conditions in multi-user environments.
  3. Flexible Updates: You can choose which fields to update on conflict.

Pro Tip: Use upsert on unique constraints like email, username, or other identifiers to ensure data consistency without extra queries.


Possible Datatypes for Insertion

When inserting data with Drizzle ORM, most common datatypes behave in a predictable and developer-friendly way.

Dates and datetimes can be passed as JavaScript Date objects or ISO strings, and Drizzle automatically converts them into the correct SQL date or timestamp formats.

Default values work effortlessly since PostgreSQL fills them in whenever you omit the field.

Decimal or numeric columns expect values as strings to avoid floating-point precision issues, while foreign keys simply require the referenced ID, with both Drizzle and PostgreSQL ensuring its validity.

Primary keys follow the same rules: auto-increment values are generated by the database, whereas UUID-based keys accept any valid UUID string if you choose to provide one. Unique constraints aren’t a datatype but do impact insertion, because the database rejects duplicate values unless you handle them with upsert logic

Enums only accept a defined set of string values, and Drizzle’s typing helps prevent mistakes before the query runs.

JSON and JSONB columns allow plain JavaScript objects or arrays, which Drizzle serializes automatically. Nullable columns can accept null, but non-nullable ones will fail if you send it.


Summary

In this guide, you learned how to insert a single row, insert multiple rows efficiently, return inserted data using .returning(), and perform conditional inserts with upserts to avoid duplicates.

You also explored how different datatypes behave during insertion, including dates, decimals, enums, UUIDs, foreign keys, JSON, null values, and timestamps.

With Drizzle ORM, all these operations become clean, predictable, and beginner-friendly — while still powerful enough for production apps.

Author

Sharukhan is the founder of Tecktol. He has worked as a software engineer specializing in full-stack web development.