remix-run/remix · error · Error

upsert requires at least one value

Error message

upsert requires at least one value

What it means

compileUpsertOperation requires the values object of an upsert operation to contain at least one column. An upsert with zero keys would generate an INSERT with no columns, which is invalid SQL, so the compiler rejects it early with a descriptive message.

Source

Thrown at packages/data-table-sqlite/src/lib/sql-compiler.ts:212

              .map((column) => {
                let value = Object.prototype.hasOwnProperty.call(row, column) ? row[column] : null
                return pushValue(context, value)
              })
              .join(', ') +
            ')',
        )
        .join(', ') +
      compileReturningClause(returning),
    values: context.values,
  }
}

function compileUpsertOperation(operation: UpsertOperation, context: CompileContext): SqlStatement {
  let insertColumns = Object.keys(operation.values)
  let conflictTarget = operation.conflictTarget ?? [...getTablePrimaryKey(operation.table)]

  if (insertColumns.length === 0) {
    throw new Error('upsert requires at least one value')
  }

  let updateValues = operation.update ?? operation.values
  let updateColumns = Object.keys(updateValues)

  let conflictClause = ''

  if (updateColumns.length === 0) {
    conflictClause =
      ' on conflict (' +
      conflictTarget.map((column: string) => quotePath(column)).join(', ') +
      ') do nothing'
  } else {
    conflictClause =
      ' on conflict (' +
      conflictTarget.map((column: string) => quotePath(column)).join(', ') +
      ') do update set ' +
      updateColumns

View on GitHub (pinned to 9696913134)

Solutions

  1. Validate that the values object has at least one key before calling upsert and skip or return a 400 otherwise.
  2. Fix the upstream construction (spread of empty object, filtered mapping) so it never produces an empty payload.

Example fix

// before
await query.upsert(table, values /* {} */)

// after
if (Object.keys(values).length === 0) {
  throw new Response('Empty payload', { status: 400 })
}
await query.upsert(table, values)
Defensive patterns

Strategy: validation

Validate before calling

if (Object.keys(values).length === 0) {
  throw new Response('Payload must include at least one column', { status: 400 })
}
await query.upsert(table, values)

Type guard

function hasUpsertValues(values: Record<string, unknown>): boolean {
  return Object.keys(values).length > 0
}

Prevention

When it happens

Trigger: Calling upsert-like APIs with values = {} or an object built from spread/loop that ends up empty, e.g. inserting {} or Object.fromEntries([]).

Common situations: Building values dynamically (e.g. sanitizing input strips every key, or mapping an empty array to an object) and passing the empty result to upsert; defaulting to {} when optional payload fields are missing.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/78c819c22bbb583a. Report an issue: GitHub.