Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

upsert

Create a record if it doesn't exist, or update it if it does

upsert updates the record matching where if it exists, otherwise creates it — in a single atomic operation.

const user = await client.user.upsert({
  where: { email: "alice@example.com" },
  create: {
    email: "alice@example.com",
    name: "Alice",
  },
  update: {
    name: "Alice Updated",
  },
});

upsert is atomic and race-safe: concurrent upserts on the same key never fail with a duplicate error — one creates, the other updates.

What the create branch needs in create

To hand back the row it just inserted, upsert has to identify that exact row. It never assumes that the where names the created row. Any one of these is enough:

  • every primary key field, as a literal;
  • every column of some unique constraint of the model (a .unique() column, or a whole compound unique), explicitly written in create;
  • a database-generated primary-key member that the active adapter can publish exactly from the INSERT. This includes a compound key whose generated members are returned by the provider.

On a non-returning adapter, one generated increment key can use the driver’s exact statement-local insert ID. A generated compound key has no singular insert ID, but it can still work when create explicitly writes another complete public unique key: VibORM uses that stable key for one focused read and publishes every generated primary-key member from the inserted row. Omitted/defaulted, null, SQL-expression, incomplete compound, and raw-index-only values are not stable locators.

If neither provider output nor a stable explicit locator can identify the row, the create branch refuses with UnsupportedOperationError before it writes. The refusal is branch-local: the same call still updates normally when the row exists.

Options

await client.user.upsert({
  where: { ... },       // Required: unique identifier
  create: { ... },      // Required: data for new record
  update: { ... },      // Required: data for existing record
  select: { ... },      // Optional: fields to return
  include: { ... },     // Optional: relations to include
});

With Relations

const user = await client.user.upsert({
  where: { email: "alice@example.com" },
  create: {
    email: "alice@example.com",
    name: "Alice",
    profile: {
      create: { bio: "New user" },
    },
  },
  update: {
    name: "Alice Updated",
    profile: {
      upsert: {
        create: { bio: "New profile" },
        update: { bio: "Updated profile" },
      },
    },
  },
  include: { profile: true },
});

Examples

User Settings

async function updateSetting(userId: string, key: string, value: string) {
  return client.userSetting.upsert({
    where: {
      userId_key: { userId, key },  // Compound unique
    },
    create: {
      userId,
      key,
      value,
    },
    update: {
      value,
    },
  });
}

Increment Counter

async function recordPageView(pageId: string) {
  return client.pageStats.upsert({
    where: { pageId },
    create: {
      pageId,
      views: 1,
      lastViewedAt: new Date(),
    },
    update: {
      views: { increment: 1 },
      lastViewedAt: new Date(),
    },
  });
}

vs Create + Update

Prefer upsert over a find-then-create/update sequence: it’s an atomic operation with no exposed race window. Use separate queries only when the two branches have genuinely different logic or you need to know which one ran.

Was this page helpful?