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

  1. Always terminate .onConflict() with .merge() or .ignore()
  2. 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

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


AI-assisted analysis of knex/knex@e25d54bcb7 (2026-08-03). Data as JSON: /data/errors/04af2712f91371d7.json. Report an issue: GitHub.