Inserts
Insert rows into ClickHouse with a type-safe TypeScript API. Row shapes are derived from your schema, with nullable columns optional and value types checked at compile time.
Inserts
The insert() method starts a type-safe insert. Row shapes are derived from your schema: Nullable(...) columns are optional, every other column is required, and value types are checked at compile time.
Basic Usage
const result = await db.insert('events')
.values({
id: 1,
name: 'signup',
created_at: new Date(), // DateTime columns accept string | Date
optional_note: null, // Nullable(...) columns are optional
})
.execute();
// result: { queryId: string; executed: boolean; summary?: unknown }Pass an array to insert multiple rows in one request, or chain values() calls — rows accumulate. An explicit empty array is a valid no-op: no request is sent and execute() resolves { executed: false }, so batch jobs don't need to special-case empty batches.
await db.insert('events')
.values([
{ id: 1, name: 'signup', created_at: new Date() },
{ id: 2, name: 'login', created_at: new Date() },
])
.execute();Type Safety
TypeScript rejects invalid inserts before they run:
db.insert('bad_table'); // ✗ unknown table
db.insert('events').values({ id: 'x' }); // ✗ wrong value type
db.insert('events').values({ id: 1 }); // ✗ missing required columns
db.insert('events').values({ nope: 1, ... }); // ✗ unknown columnValue types are widened where ClickHouse accepts more than one input format:
- DateTime / DateTime64 columns accept
string | Date | number(Datevalues are converted to ISO-8601 strings; numbers are epoch seconds). - Date / Date32 columns accept
'YYYY-MM-DD'strings only — ClickHouse's JSONEachRow parser rejects datetime strings for Date columns, so the types don't permitDateobjects there. This also avoids the classic timezone bug where aDatecreated at local midnight lands on a different UTC calendar day. - Int64 and larger integers accept
string | number | bigint(bigintvalues are converted to decimal strings). - Decimals accept
number | string. - Enums accept the name or the numeric value.
NaN and Infinity are rejected with an error before the request is sent — JSON serialization would otherwise silently coerce them to null.
Inserting a Subset of Columns
Use columns() to insert only some columns and let ClickHouse fill table DEFAULTs for the rest. Call it before values() — the accepted row shape narrows to the selected columns:
await db.insert('events')
.columns(['id', 'name'])
.values([
{ id: 1, name: 'signup' },
{ id: 2, name: 'login' },
])
.execute();Columns with DEFAULT expressions
The generated schema doesn't carry DEFAULT metadata, so defaulted columns are still
required in the full-width row shape. Use columns() to omit them.
Insert Settings
Apply per-insert ClickHouse settings with settings() — useful for async inserts:
await db.insert('events')
.values(rows)
.settings({ async_insert: 1, wait_for_async_insert: 1 })
.execute();You can also pass a queryId for tracing: .execute({ queryId: 'my-insert' }).
How It Works
Inserts run through the ClickHouse client's native insert path using the JSONEachRow format — values are never interpolated into SQL text. date_time_input_format: 'best_effort' is set by default so ISO-8601 timestamps parse into DateTime columns (your settings take precedence).
All rows in a values() call are buffered and sent as a single request. For very large volumes, insert in batches (streaming inserts are planned).
Custom adapters
Inserts require the adapter to implement the optional DatabaseAdapter.insert method.
The built-in ClickHouse adapter supports it; custom adapters that don't will throw a clear error.
Query cache
Inserts do not invalidate the query result cache. Cached reads refresh when their TTL expires.