insert
Bulk-insert raw rows. Faster than create for large batches because it skips model events, attribute casts, and hydration. The opposite trade-off too — you don't get back hydrated models.
Signature
QueryBuilder.insert(values: any[] | Record<string, any>): Promise<boolean>Returns true once the INSERT has executed. Wrap in a transaction if you need atomicity across multiple inserts.
Real-World Use Cases
1. Many rows in one statement
await db().query().from('users').insert([
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Carol', email: 'carol@example.com' },
]);Compiles to a single multi-row INSERT — one round trip.
2. Off the model
// Dispatched from a model — same multi-row insert, scoped to the model's connection.
await User.createQuery().insert([
{ id: 1, email: 'a@x.com' },
{ id: 2, email: 'b@x.com' },
]);3. Insert + retrieve last id — insertGetId
When you need the auto-generated key:
const id = await db().query().from('orders').insertGetId({
user_id: 1,
total: 99,
});The second arg is the sequence column name (default id) — needed for some drivers like PostgreSQL where currval() is sequence-scoped.
4. Insert ignoring duplicates — insertOrIgnore
const inserted = await db().query().from('subscribers').insertOrIgnore([
{ email: 'a@x.com' },
{ email: 'b@x.com' },
]);Returns the number of rows actually inserted. Driver-native: INSERT IGNORE (MySQL), ON CONFLICT DO NOTHING (Postgres), etc.
5. Driver-native upsert — upsert
await db().query().from('daily_revenue').upsert(
[
{ day: '2026-05-01', cents: 100 },
{ day: '2026-05-02', cents: 250 },
],
['day'], // conflict columns
['cents'], // columns to update on conflict
);Compiles to INSERT ... ON DUPLICATE KEY UPDATE (MySQL), INSERT ... ON CONFLICT (...) DO UPDATE (Postgres).
For per-row "find then upsert" with model events, use updateOrCreate.
6. Insert from a subquery — insertUsing
await db().query().from('archived_orders').insertUsing(
['id', 'total', 'archived_at'],
(q) => q.from('orders').where('completed', true).select('id', 'total', db().raw('NOW()')),
);Atomic copy from one table to another, no rows hydrated.
insert vs create
| Tool | Hydrates result? | Fires model events? | Casts/accessors? |
|---|---|---|---|
create | ✓ | ✓ (creating/created/saved) | ✓ |
insert | ✗ (boolean) | ✗ | ✗ — values written verbatim |
insertGetId | ✗ (returns id) | ✗ | ✗ |
Pick insert when you've got pre-validated data and you don't need the model API on the result.
Common Pitfalls
- No casts: a
JsonColumncolumn needs you toJSON.stringifythe value yourself. - No
created_at/updated_atmagic: the bulk path doesn't auto-fill timestamps. Add them to each row in the array. - Watch the array shape: passing a single object inserts one row; passing an array inserts many. Mixing the two (e.g. forgetting to wrap) is the easiest typo.
See Also
create— single-row insert with hydration.updateOrCreate— per-row upsert with model events.upsert(query builder) — driver-native bulk upsert.