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
- Skip the upsert when the values object is empty (guard with Object.keys(values).length > 0)
- Default missing payloads to a meaningful sentinel column or treat empty as a no-op earlier in the flow
- 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
- Validate payloads have at least one field before building upserts
- Guard bulk-sync loops that map possibly-empty objects to operations
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
- Unsupported operation kind
- upsert requires at least one value
- Unknown transaction token: + token.id
- MySQL migration lock is already held by this database
- MySQL database + method + () requires config-based construc
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/fe44b93e54854364.
Report an issue: GitHub.