knex/knex · error · Error
Incomplete onConflict clause. .onConflict() must be directly
Error message
Incomplete onConflict clause. .onConflict() must be directly followed by either .merge() or .ignore()
What it means
onConflict() returns an OnConflictBuilder that must be terminated by .merge() or .ignore() to set the actual conflict resolution. If the builder is awaited/executed while still on the OnConflictBuilder (i.e. neither terminator was called), its then() throws. This prevents silently running an INSERT that ignores the conflict intent the developer expressed.
Source
Thrown at lib/query/querybuilder.js:1787
}
// Sets insert query to ignore conflicts
ignore() {
this.builder._single.onConflict = this._columns;
this.builder._single.ignore = true;
return this.builder;
}
// Sets insert query to update on conflict
merge(updates) {
this.builder._single.onConflict = this._columns;
this.builder._single.merge = { updates };
return this.builder;
}
// Prevent
then() {
throw new Error(
'Incomplete onConflict clause. .onConflict() must be directly followed by either .merge() or .ignore()'
);
}
}
module.exports = Builder;
View on GitHub (pinned to e25d54bcb7)
Solutions
- Always terminate .onConflict() with .merge() or .ignore()
- Ensure any conditional logic that adds onConflict also adds the terminator unconditionally
Example fix
// before
await knex('users').insert({id:1, n:'a'}).onConflict('id');
// after
await knex('users').insert({id:1, n:'a'}).onConflict('id').merge(); Defensive patterns
Strategy: validation
Validate before calling
function completeOnConflict(builder, columns, mode) {
const oc = builder.onConflict(columns);
return mode === 'ignore' ? oc.ignore() : oc.merge();
} Type guard
null
Try / catch
try {
await knex('t').insert(v).onConflict('id');
} catch (e) {
if (/Incomplete onConflict/.test(e.message)) { /* add .merge()/.ignore() */ }
else throw e;
} Prevention
- Always chain .merge() or .ignore() immediately after .onConflict()
- Use a helper that returns a completed OnConflictBuilder so it can never be left dangling
- Lint for .onConflict( not followed by .merge( or .ignore(
When it happens
Trigger: Calling knex('t').insert(...).onConflict('col') and then awaiting/executing it without chaining .merge() or .ignore(). Also triggered by .toString()/.toSQL() on the incomplete OnConflictBuilder.
Common situations: Developer forgets to finish the clause, or a conditional branch that adds .merge()/.ignore() is skipped at runtime.
Related errors
- .onConflict().merge().where() is not supported for mysql
- If using merge with a raw insert query, then updates must be
- .onConflict() is not supported for oracledb.
- Cannot chain .first() on "${this._method}" query
- Cannot chain .pluck() on "${this._method}" query
AI-assisted analysis of knex/knex@e25d54bcb7 (2026-08-03).
Data as JSON: /data/errors/04af2712f91371d7.json.
Report an issue: GitHub.