update
Calling update on a query writes every record the query matches:
// Update matching records — loads each record and calls instance update(), running
// lifecycle hooks and validations
await LocalizedText.where({ localizable: host, locale: 'en-US' }).update({ title: 'New Title' })
This is not a Rails-style update_all. By default it iterates the matched records with findEach and calls instance .update() on each one, running per-record hooks and validations. This distinction matters in both directions: hook-enforced invariants are still enforced by default query updates, and a default update issues one UPDATE per matched row.
Because it goes through findEach, a default (non-skipHooks) query update always visits matched records in ascending primary-key order and ignores any order you applied to the query — see findEach for why batch processing can't honor an arbitrary order.
update resolves to the number of records it wrote. In the default form that's how many records it visited and wrote through — one already holding the incoming values still counts.
A query that carries a limit or offset throws BatchingIncompatibleWithLimitOrOffset in every form of update: a batched run would re-apply either inside each batch rather than bounding the run as a whole, and a single UPDATE ... WHERE statement can't express them.
Skipping hooks
Pass { skipHooks: true } to suppress the callback lifecycle and issue a single UPDATE ... WHERE statement instead — no model instantiated, no hooks, no validations, no custom setters. The statement is still filtered by the query's where and the model's default scopes:
// One UPDATE ... WHERE statement — no hooks, no validations
await LocalizedText.where({ localizable: host, locale: 'en-US' }).update({ title: 'New Title' }, { skipHooks: true })
skipHooks is the bulk path, and its price is the lifecycle. It's the idiomatic way to write many rows in one SQL statement — there is no other way, short of dropping to toKysely for the same statement. What it removes is the callback lifecycle, so before reaching for it, confirm that the model's hooks carry no business logic that applies to this write. The safety judgment is per write, not per model: a hook guarding a status transition makes a bulk status write unsafe to skip, but says nothing about a bulk write to an unrelated column on the same model. When the hooks do apply, the fix isn't to skip them — keep the default per-record update, or narrow the query. Under { skipHooks: true }, the count returned is the number of rows the single UPDATE ... WHERE statement matched.
Claiming a record safely: { lock: true }
When the value you're writing depends on a value you just read — claiming a record out of a state, where a concurrent writer must not clobber the result — use { lock: true }. It re-checks the query's where under a row lock before writing each record, and hooks and validations still run. See the locking guide.