remix-run/remix · error · Error

upsert requires at least one value

Error message

upsert requires at least one value

What it means

compileUpsertOperation throws when the values object of an upsert operation has no keys, because MySQL INSERT ... ON DUPLICATE KEY UPDATE requires at least one column to insert. An empty payload makes the generated SQL invalid, so the compiler rejects it up front.

Source

Thrown at packages/data-table-mysql/src/lib/sql-compiler.ts:192

  )

  return {
    text:
      'insert into ' +
      quotePath(getTableName(table)) +
      ' (' +
      columns.map((column) => quotePath(column)).join(', ') +
      ') values ' +
      values.join(', '),
    values: context.values,
  }
}

function compileUpsertOperation(operation: UpsertOperation, context: CompileContext): SqlStatement {
  let insertColumns = Object.keys(operation.values)

  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 fallbackNoopColumn = getTablePrimaryKey(operation.table)[0]

  let onDuplicate =
    updateColumns.length > 0
      ? updateColumns
          .map((column) => quotePath(column) + ' = ' + pushValue(context, updateValues[column]))
          .join(', ')
      : quotePath(fallbackNoopColumn) + ' = ' + quotePath(fallbackNoopColumn)

  return {
    text:
      'insert into ' +
      quotePath(getTableName(operation.table)) +
      ' (' +

View on GitHub (pinned to 9696913134)

Solutions

  1. Skip the upsert when the values object is empty (guard with Object.keys(values).length > 0)
  2. Default missing payloads to a meaningful sentinel column or treat empty as a no-op earlier in the flow
  3. Validate API payloads before constructing database operations

Example fix

// before
await upsert(driver, 'users', payload) // payload === {}
// after
if (Object.keys(payload).length > 0) {
  await upsert(driver, 'users', payload)
}
Defensive patterns

Strategy: validation

Validate before calling

if (Object.keys(values).length === 0) {
  // nothing to upsert — skip
} else {
  await upsert(driver, 'users', values)
}

Type guard

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

Try / catch

try { const stmt = compileMysqlOperation(op, ctx) } catch (e) { if (e instanceof Error && e.message === 'upsert requires at least one value') { return /* no-op */ } throw e }

Prevention

When it happens

Trigger: Calling the upsert builder/compileMysqlOperation with values = {} (e.g. Object.keys(payload).length === 0 after filtering); spreading an empty request body or an emptied object into upsert values.

Common situations: Bulk-sync code upserting rows from user input where all fields were stripped by validation/sanitization; mapping over an empty dataset but still emitting upsert operations; optional-payload paths that default to {}.

Related errors


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