drizzle-team/drizzle-orm · error · Error
values() must be called with at least one value
Error message
values() must be called with at least one value
What it means
PgInsertBuilder.values (insert.ts:84) normalizes its argument to an array and throws if that array is empty (insert.ts:88). Drizzle cannot generate an INSERT statement without at least one row, so an empty batch is treated as a programmer error rather than emitted as no-op SQL.
Source
Thrown at drizzle-orm/src/pg-core/query-builders/insert.ts:89
/** @internal */
setToken(token?: NeonAuthToken) {
this.authToken = token;
return this;
}
overridingSystemValue(): Omit<PgInsertBuilder<TTable, TQueryResult, true>, 'overridingSystemValue'> {
this.overridingSystemValue_ = true;
return this as any;
}
values(value: PgInsertValue<TTable, OverrideT>): PgInsertBase<TTable, TQueryResult>;
values(values: PgInsertValue<TTable, OverrideT>[]): PgInsertBase<TTable, TQueryResult>;
values(
values: PgInsertValue<TTable, OverrideT> | PgInsertValue<TTable, OverrideT>[],
): PgInsertBase<TTable, TQueryResult> {
values = Array.isArray(values) ? values : [values];
if (values.length === 0) {
throw new Error('values() must be called with at least one value');
}
const mappedValues = values.map((entry) => {
const result: Record<string, Param | SQL> = {};
const cols = this.table[Table.Symbol.Columns];
for (const colKey of Object.keys(entry)) {
const colValue = entry[colKey as keyof typeof entry];
result[colKey] = is(colValue, SQL) ? colValue : new Param(colValue, cols[colKey]);
}
return result;
});
return new PgInsertBase(
this.table,
mappedValues,
this.session,
this.dialect,
this.withList,
false,View on GitHub (pinned to b7862528fd)
Solutions
- Guard the call: only invoke .values() when the row array has length > 0.
- If empty input is legitimate, treat it as a no-op by skipping the query entirely.
- Validate upstream payload size before constructing the insert.
Example fix
// before await db.insert(users).values(rows.filter(isValid)); // crashes when empty // after const valid = rows.filter(isValid); if (valid.length) await db.insert(users).values(valid);
Defensive patterns
Strategy: validation
Validate before calling
const rows = payload.filter(isValid);
if (rows.length === 0) {
// nothing to insert; skip instead of crashing
return { inserted: 0 };
}
await db.insert(users).values(rows); Type guard
function isNonEmpty<T>(arr: T[]): arr is [T, ...T[]] {
return arr.length > 0;
} Prevention
- Guard bulk inserts with an array-length check.
- Treat empty input as a legitimate no-op in batch loaders.
- Validate payload size at the API boundary.
When it happens
Trigger: Calling db.insert(t).values([]) directly, or passing a runtime-filtered array of rows that happens to be empty: db.insert(t).values(rows.filter(isValid)) when no rows pass the filter.
Common situations: Bulk-inserting from a CSV/payload that yielded zero valid rows; processing a webhook batch that is occasionally empty; refactoring to deduplicate rows and accidentally producing [].
Related errors
- values() must be called with at least one value
- Insert select error: selected fields are not the same or are
- Insert select error: selected fields are not the same or are
- You cannot use both "where" and "targetWhere"/"setWhere" at
- values() must be called with at least one value
AI-assisted analysis of drizzle-team/drizzle-orm@b7862528fd (2026-08-03).
Data as JSON: /data/errors/bf431938adc6aa11.json.
Report an issue: GitHub.