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

  1. Check the source data: ensure at least one row in the batch has at least one column value
  2. Guard before calling: if (rows.every(r => Object.keys(r).length === 0)) skip or reject the request
  3. 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

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


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