Prisma ORM 8 is here.Read the docs

Writing data

Create, update, delete, and upsert records with Prisma ORM, one at a time or in bulk.

This page shows how to write data with Prisma ORM: creating, updating, deleting, and upserting single records, and writing many records at once.

Every example imports db. New project: npm create prisma@latest -- my-app. Existing project: npx prisma orm init. Either way you get src/prisma/db.ts, which exports db. The import is ./prisma/db from a file in src/.

This page uses db.orm, which holds your models. The rest of db is db.sql for the SQL query builder, db.raw.sql for raw SQL, and db.transaction for running several writes together.

On PostgreSQL the path to a model is db.orm.<schema>.<ModelName>, so the User model is db.orm.public.User. public is the PostgreSQL schema; you type it. It is public unless the model sits in a namespace block (shown under Example schema). On MongoDB there is no schema segment. The path is db.orm.<collectionName>, so the model below is db.orm.users. The collection name is the model's @@map(...), or the model name with a lowercase first letter.

Example schema

Examples use this contract. In Prisma ORM 8 the schema file is contract.prisma instead of schema.prisma; the docs call it your contract. @default(cuid(2)) means Prisma ORM generates the id for you, so you never pass one:

Expand for sample schema
model User {
  id        String   @id @default(cuid(2))
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  posts     Post[]
}

model Post {
  id        String   @id @default(cuid(2))
  title     String
  content   String?
  published Boolean
  authorId  String
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())
}

To put models in another PostgreSQL schema, wrap them in a namespace block:

namespace billing {
  model Invoice {
    id     String @id @default(cuid(2))
    amount Int
  }
}

The model is then only at db.orm.billing.Invoice, and the block name is the PostgreSQL schema name.

Create one record

Use .create(...) to insert one record. Pass the fields directly. Prisma ORM returns the inserted record, including generated values such as IDs and database defaults:

import { db } from "./prisma/db";

const user = await db.orm.public.User.create({
  email: "jane@prisma.io",
  name: "Jane",
});
// user.id and user.createdAt are filled in

The returned record is complete, so you can use the generated values right away (PostgreSQL shown; on MongoDB the key is _id):

{ id: 'cuid20000000000000000003', email: 'jane@prisma.io', name: 'Jane', createdAt: 2026-07-06T09:09:56.119Z }

To get back only some fields, chain .select(...) before .create(...). The record is still inserted in full. You only get back the fields you listed:

const account = await db.orm.public.User
  .select("id", "email")
  .create({ email: "jane@prisma.io", name: "Jane" });
{ id: 'cuid20000000000000000003', email: 'jane@prisma.io' }

.select(...) works the same before update, delete, upsert, createAll, updateAll, and deleteAll. The AndCount methods give you back a number, so .select(...) has no effect on them.

When a write breaks a unique constraint, the call throws. Errors have no shared class. A PostgreSQL database error carries sqlState (a five-character SQL state code), a MongoDB driver error carries a numeric code, and a Prisma ORM error carries a string code such as RUNTIME.ITERATOR_CONSUMED. On PostgreSQL, 23505 is the SQL state code for a unique violation. The error reference lists Prisma ORM's own codes:

try {
  await db.orm.public.User.create({ email: "jane@prisma.io", name: "Jane" });
} catch (error) {
  if ((error as { sqlState?: string }).sqlState === "23505") {
    // that email is already taken
  }
}

On MongoDB the driver throws its own error. A duplicate key gives you an error whose error.code is 11000.

To write related records in the same call, pass a callback for the relation field. The callback's argument, named p below, is the relation builder, which holds the methods that link or insert related records:

const user = await db.orm.public.User.create({
  email: "jane@prisma.io",
  name: "Jane",
  posts: (p) =>
    p.create([{ title: "First post", content: null, published: false }]),
});

You do not pass authorId on the nested posts. Prisma ORM fills it in from the user it just inserted.

connect links a record that already exists, and works in .update(...) too:

await db.orm.public.User
  .where({ email: "jane@prisma.io" })
  .update({ posts: (p) => p.connect([{ id: existingPostId }]) });

p.disconnect(...) unlinks a related record. It applies on .update(...) only, not on .create(...).

connectOrCreate, the relation set, and nested updates, upserts, and deletes do not exist. See Not available. For the full picture of relations, see Relations and joins.

Update one record

Use .where(...) to pick the record, then .update(...) with the fields to change. It updates one matching record and returns it:

const updatedUser = await db.orm.public.User
  .where({ email: "jane@prisma.io" })
  .update({ name: "Jane Doe" });
{ id: 'cuid20000000000000000003', email: 'jane@prisma.io', name: 'Jane Doe', createdAt: 2026-07-06T09:09:56.119Z }

When nothing matches the filter, .update(...) returns null. It does not throw.

When the filter matches more than one record, .update(...) still changes only one of the matching records, with no guaranteed order. Use updateAll or updateAndCount if you mean all of them.

On MongoDB you can also pass a callback instead of an object, and change a field with an operation rather than a value. The callback's argument, named p here, gives you one entry per field, and each field carries the operations you can apply to it:

await db.orm.posts
  .where({ title: "Draft thoughts" })
  .update((p) => [p.content.set("Now filled in"), p.published.set(true)]);

The callback returns an array, so you can apply several operations in one update.

set and unset work on any field. inc and mul are on number fields only. push, pull, addToSet, and pop are for array fields, and you call them the same way: p.tags.push("news"). These field operations are MongoDB only. PostgreSQL has no callback form of update, so on PostgreSQL you pass an object. See Field update operations in the reference.

There is no increment on PostgreSQL. To add to a number in place, write the update as raw SQL. For a views Int column added to Post, db.raw.sql writes a raw statement, .affectedCount() says you want the row count back as { affectedRows }, .build() finishes it, and db.runtime().execute(...) runs it; Advanced queries explains raw SQL:

const query = db.raw.sql`UPDATE post SET views = views + 1 WHERE id = ${postId}`
  .affectedCount()
  .build();
const { affectedRows } = await db.runtime().execute(query);

Delete one record

Use .where(...) then .delete(). It deletes one matching record and returns it:

const deletedUser = await db.orm.public.User
  .where({ email: "jane@prisma.io" })
  .delete();

.delete() returns null when nothing matches, and deletes only one record when several match. See Update one record. To delete every match, use deleteAll or deleteAndCount.

Upsert a record

Use .upsert(...) to update a record if it exists and create it otherwise. Pass the two branches separately:

await db.orm.public.User.upsert({
  create: { email: "eve@prisma.io", name: "Eve" },
  update: { name: "Eve Exists" },
  conflictOn: { email: "eve@prisma.io" },
});

conflictOn repeats the unique field and its value from create; Prisma ORM uses it to look for an existing row. For a unique constraint over several columns, pass them all in one object: conflictOn: { tenantId, email }. Without conflictOn on PostgreSQL, the upsert looks for a row by primary key, which a new record does not have, so the insert runs and fails on the unique constraint. Always pass conflictOn.

On MongoDB, there is no conflictOn. Put the match in .where(...) before .upsert(...).

Write many records

Use the All and AndCount methods when you intend to write every record you pass, or change every record the filter matches:

const user = await db.orm.public.User.first({ email: "jane@prisma.io" });
if (!user) throw new Error("no such user");
// Insert many records
const newPosts = await db.orm.public.Post.createAll([
  { title: "One", content: null, published: false, authorId: user.id },
  { title: "Two", content: null, published: false, authorId: user.id },
]);

// Insert many, get back only the number inserted
const insertedCount = await db.orm.public.Post.createAndCount([
  { title: "Three", content: null, published: false, authorId: user.id },
]);

// Update every match, get back only the number updated
const updatedCount = await db.orm.public.Post.where({ published: false }).updateAndCount({ published: true });

// Delete every match, get back the deleted records
const deletedPosts = await db.orm.public.Post.where({ published: false }).deleteAll();

// Delete every match, get back only the number deleted
const deletedCount = await db.orm.public.Post.where((p) => p.title.ilike("draft%")).deleteAndCount();

createAll gives you back the inserted records, with their generated IDs:

[{ id: 'cuid20000000000000000101', title: 'One', published: false, /* ... */ }, { id: 'cuid20000000000000000102', title: 'Two', published: false, /* ... */ }]

The AndCount methods return a plain number. If three posts match, updatedCount is 3, not an object.

.where((p) => p.title.ilike("draft%")) is the callback form of a filter, for conditions an object cannot express. Its argument gives you one entry per field, and each field carries the comparisons you can apply to it. Reading data covers the filters you can write.

The bulk methods work the same on MongoDB, on the collection: db.orm.posts. Pass createdAt in every object you give createAll, as with create.

Return rows or counts

Each write comes in three forms. Pick by what you need back:

FormWhat it writesWhat you get back
create, update, deleteone recordthat record
createAll, updateAll, deleteAllevery record you pass, or every matchthose records
createAndCount, updateAndCount, deleteAndCountevery record you pass, or every matchthe number of records written

update and delete return null when nothing matches. Use the AndCount forms when the number is all you need, because they do not send the records back.

The All forms return a result you can use two ways. Nothing is sent to the database until you await the result or loop over it, so an updateAll you never await changes nothing. await it for an array of records:

const publishedPosts = await db.orm.public.Post
  .where({ published: false })
  .updateAll({ published: true });
[{ id: 'cuid20000000000000000101', title: 'One', published: true, /* ... */ }, { id: 'cuid20000000000000000102', title: 'Two', published: true, /* ... */ }]

Or iterate it with for await to handle records as they arrive:

const updated = db.orm.public.Post.where({ published: false }).updateAll({ published: true });

for await (const post of updated) {
  console.log(post.id);
}

Pick one: await it or loop it with for await. Mixing the two throws an error whose error.code is RUNTIME.ITERATOR_CONSUMED.

Common mistakes

Updating or deleting more than one record

You filtered on a non-unique field and expected every match to change:

await db.orm.public.Post.where({ published: false }).update({ published: true });

.update(...) and .delete() only change one record, even when the filter matches many. See Update one record.

When you intend to affect every match, say so with the bulk methods:

const updatedCount = await db.orm.public.Post
  .where({ published: false })
  .updateAndCount({ published: true });

Use updateAll or deleteAll when you also need the changed records back, and updateAndCount or deleteAndCount when the number is enough. update and delete never change more than one record, so they stay safe for the one-record case.

Wrapping create fields in a data object

You wrote the Prisma ORM 7 shape, and it fails type-checking. There is no field named data on your models:

- await db.orm.public.User.create({ data: { email, name } });
+ await db.orm.public.User.create({ email, name });

You pass the record's own fields, and you get the record back.

Updating or deleting without a filter

You called .update(...) or .delete() straight on the model:

await db.orm.public.User.delete();

Both need a .where(...) first, and the call does not type-check without one. If you truly mean every record, pass an empty filter, which adds no condition and so matches everything. .delete() needs a .where(...) for the same reason, and .where({}) satisfies it there too:

await db.orm.public.User.where({}).deleteAll();

You created a user, then created their first post as a second await:

const user = await db.orm.public.User.create({ email, name });
const post = await db.orm.public.Post.create({ title, published: false, authorId: user.id });

If the second write fails, the first has already committed, and you're left with half the operation. When writes must succeed together, run them in a transaction. Inside the callback, query through tx instead of db:

await db.transaction(async (tx) => {
  const user = await tx.orm.public.User.create({ email, name });
  await tx.orm.public.Post.create({ title, published: false, authorId: user.id });
});

Passing an array of queries to a transaction

Prisma ORM 7 supported $transaction([query1, query2]). Prisma ORM 8 does not: there is no $transaction, and queries don't queue up in arrays. Put the calls inside one db.transaction(...) callback instead. The Transactions page shows the pattern.

Prompt your coding agent

Projects created with npm create prisma@latest include the Prisma ORM skills for your coding agent; in an existing project, run npx prisma skills sync. Skills are instruction files that tell the agent how Prisma ORM 8 works, and the prisma-8 skill covers everything on this page. Prompts that map to each section:

  • "Using the prisma-8 skill, add a signup function that creates a User and returns only its id and email."
  • "Write an upsert that creates a user by email or updates their name if they exist."
  • "This cleanup script must delete every draft older than 30 days. Use the bulk delete method and log how many records were removed."
  • "Review my mutations for places where .update() should be updateAll or updateAndCount."

Next

On this page