remix-run/remix · error · DataTableQueryError
insertMany() requires at least one explicit value across the
Error message
insertMany() requires at least one explicit value across the batch
What it means
insertMany() validates that the prepared batch contains at least one explicit column value overall. If every row object in the batch is empty ({} for each), the generated INSERT would have no columns at all, which no database can execute, so a DataTableQueryError is thrown. Note the check is across the batch: an individual empty row is tolerated as long as some other row has values.
Source
Thrown at packages/data-table/src/lib/database/query-execution.ts:315
insertId: result.insertId,
}
}
async function executeInsertMany(
database: QueryExecutionContext,
table: AnyTable,
values: Record<string, unknown>[],
options?: { returning?: ReturningInput<Record<string, unknown>>; touch?: boolean },
): Promise<WriteResult | WriteRowsResult<Record<string, unknown>>> {
let preparedValues = values.map((value) =>
prepareInsertValues(table, value as never, database.now(), options?.touch ?? true),
)
if (
preparedValues.length > 0 &&
preparedValues.every((preparedValue) => Object.keys(preparedValue).length === 0)
) {
throw new DataTableQueryError(
'insertMany() requires at least one explicit value across the batch',
)
}
let returning = options?.returning
assertReturningCapability(database.capabilities, 'insertMany', returning)
if (returning) {
let operation: InsertManyOperation<AnyTable> = {
kind: 'insertMany',
table,
values: preparedValues,
returning: normalizeReturningSelection(returning),
}
let result = await database[executeOperation](operation)
let affectedRows = result.affectedRows ?? 0
runAfterWriteHook(table, {View on GitHub (pinned to 9696913134)
Solutions
- Check the source data: ensure at least one row in the batch has at least one column value
- Guard before calling: if (rows.every(r => Object.keys(r).length === 0)) skip or reject the request
- Fix the upstream mapping/normalization that is stripping all fields from each row
Example fix
// before
await table.insertMany({ values: rows.map(() => ({})) })
// after
let batch = rows.map(r => ({ name: r.name, email: r.email }))
if (batch.some(r => Object.keys(r).length > 0)) {
await table.insertMany({ values: batch })
} Defensive patterns
Strategy: validation
Validate before calling
let hasValues = batch.some((row) => Object.keys(row).length > 0)
if (!hasValues) throw new Error('Nothing to insert') Type guard
function isNonEmptyInsertBatch(batch: Record<string, unknown>[]): boolean {
return batch.some((row) => Object.keys(row).length > 0)
} Try / catch
try {
await table.insertMany({ values: batch })
} catch (error) {
if (error instanceof DataTableQueryError && /at least one explicit value/.test(error.message)) {
return { inserted: 0 } // no-op batch
} else throw error
} Prevention
- Validate upstream data mapping before batching (assert at least one field per source row survives)
- Reject empty CSV/form uploads before reaching the database layer
- Log row-shape statistics when building batches to catch mapping regressions
When it happens
Trigger: await table.insertMany({ values: [{}, {}] }), or values mapped from an array where every element failed to pick up any fields (e.g. destructuring bug or all fields undefined-stripped).
Common situations: Building the batch from request bodies or CSV rows where the field mapping dropped every key (wrong casing, empty file, filter removing all properties); accidentally passing an array of empty objects after normalizing undefined values away.
Related errors
- upsert requires at least one value
- upsert requires at least one value
- update() requires at least one change
- Invalid afterRead callback result for table "' + tableName +
- Invalid beforeDelete callback result for table "' + context.
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/d0748a6b05e07955.
Report an issue: GitHub.