← Files ClickHouseARCHIVED FILE
skills/clickhouse-js-node-coding/reference/query-parameters.md
5.42 KB · Sep 30, 2026 · 22:50 UTC
# Query Parameter Binding
> **Applies to:** all versions. NULL parameter binding fixed in `0.0.16`.
> Special-character (tab/newline/quote/backslash) binding `>= 0.3.1`.
> `TupleParam` and JS `Map` parameters `>= 1.9.0`. Boolean formatting in
> `Array`/`Tuple`/`Map` parameters fixed in `>= 1.13.0`. `BigInt` query
> parameters `>= 1.15.0`.
## Answer checklist
When the user passes user-controlled values into SQL:
- Use ClickHouse `{name: Type}` placeholders and a `query_params` object.
- **Your response must explicitly name template-literal / string
interpolation of user input as a SQL injection risk** — even when the
user only asked "how do I bind values" and did not mention security.
This is non-negotiable: the security framing is part of the right
answer, not an optional aside.
- Do not suggest PostgreSQL/MySQL-style `$1`, `?`, or `:name` placeholders.
- Pick the placeholder type to match the ClickHouse column type (`String`,
`Date`, `DateTime`, `Nullable(T)`, etc.).
## Syntax: `{name: Type}`
ClickHouse uses `{name: Type}` placeholders — **not** `$1`, `?`, or `:name`.
```ts
await client.query({
query: "SELECT plus({a: Int32}, {b: Int32})",
format: "JSONEachRow",
query_params: { a: 10, b: 20 },
});
```
The `Type` must be a valid ClickHouse type (`Int32`, `String`, `Date`,
`Array(UInt32)`, `Tuple(Int32, String)`, `Map(K, V)`, `Nullable(T)`, etc.).
## ⚠️ Never use template literals for user values
Interpolating user input into the SQL string bypasses server-side escaping
and opens the door to SQL injection:
```ts
const userId = req.params.id;
// ❌ Dangerous — never do this with user-controlled values
await client.query({ query: `SELECT * FROM users WHERE id = ${userId}` });
// ✓ Safe — parameterized
await client.query({
query: "SELECT * FROM users WHERE id = {id: UInt32}",
query_params: { id: userId },
});
```
This is the most common mistake for users coming from PostgreSQL/MySQL. Call
it out explicitly when the user shows template-literal interpolation.
## Common types
```ts
import { TupleParam } from "@clickhouse/client";
await client.query({
query: `
SELECT
{var_int: Int32} AS var_int,
{var_float: Float32} AS var_float,
{var_str: String} AS var_str,
{var_array: Array(Int32)} AS var_array,
{var_tuple: Tuple(Int32, String)} AS var_tuple,
{var_map: Map(Int, Array(String))} AS var_map,
{var_date: Date} AS var_date,
{var_datetime: DateTime} AS var_datetime,
{var_datetime64_3: DateTime64(3)} AS var_datetime64_3,
{var_datetime64_9: DateTime64(9)} AS var_datetime64_9,
{var_decimal: Decimal(9, 2)} AS var_decimal,
{var_uuid: UUID} AS var_uuid,
{var_ipv4: IPv4} AS var_ipv4,
{var_null: Nullable(String)} AS var_null
`,
format: "JSONEachRow",
query_params: {
var_int: 10,
var_float: "10.557",
var_str: "hello",
var_array: [42, 144],
var_tuple: new TupleParam([42, "foo"]), // >= 1.9.0
var_map: new Map([
[42, ["a", "b"]],
[144, ["c", "d"]],
]), // >= 1.9.0
var_date: "2022-01-01",
var_datetime: "2022-01-01 12:34:56", // or a Date
var_datetime64_3: "2022-01-01 12:34:56.789", // or a Date
var_datetime64_9: "2022-01-01 12:34:56.123456789", // string for ns precision
var_decimal: "123.45", // string to avoid precision loss
var_uuid: "01234567-89ab-cdef-0123-456789abcdef",
var_ipv4: "192.168.0.1",
var_null: null, // fixed in 0.0.16
},
});
```
### Type-by-type tips
- **Decimals** — pass as strings to avoid JS number precision loss.
- **`DateTime64(>3)`** — pass as a string; JS `Date` only has millisecond
precision and will lose sub-millisecond digits.
- **`DateTime64`** — strings can also be UNIX timestamps, including
fractional ones (e.g., `'1651490755.123456789'`).
- **`BigInt`** — supported in `query_params` since `>= 1.15.0`. On older
clients, pass as a string.
- **`Tuple(...)`** — wrap in `new TupleParam([...])` (`>= 1.9.0`); on older
clients, build the literal manually as a string.
- **`Map(K, V)`** — pass a JS `Map` (`>= 1.9.0`); on older clients, build
it manually.
- **`Nullable(T)`** — pass `null` directly (`>= 0.0.16`).
## Special characters in string parameters (`>= 0.3.1`)
Tabs, newlines, carriage returns, single quotes, and backslashes are
escaped automatically by the client — just pass the JS string as-is:
```ts
await client.query({
query: `
SELECT
'foo_\t_bar' = {tab: String} AS has_tab,
'foo_\n_bar' = {newline: String} AS has_newline,
'foo_\\'_bar' = {single_quote: String} AS has_single_quote,
'foo_\\_bar' = {backslash: String} AS has_backslash
`,
format: "JSONEachRow",
query_params: {
tab: "foo_\t_bar",
newline: "foo_\n_bar",
single_quote: "foo_'_bar",
backslash: "foo_\\_bar",
},
});
```
## Common pitfalls
- **`$1` / `?` / `:name` placeholders.** None work — use `{name: Type}`.
- **Forgetting the type in the placeholder.** `{id}` is a syntax error;
it must be `{id: UInt32}`.
- **Stringifying tuples/maps manually on `>= 1.9.0`.** Use `TupleParam`
and `Map` — both serialize correctly and respect special characters.
- **Boolean array/tuple/map elements before `1.13.0`.** Boolean formatting
was fixed in 1.13.0 — earlier versions may misformat them.
SHA-256: cbd30281f736cf93304355c05da9e4e88f5b22cb46d51e6e0d45752701150004