ClickHouse shipped native WASM UDF support in v26.3 LTS (2026-03-26), marked experimental. It is actively developed — ~20 PRs merged in the four months since release — but carries material limitations: the feature requires a server-level opt-in flag, is not available on ClickHouse Cloud, and the sandboxing/ABI surface is still evolving. For self-managed clusters on v26.3+, WASM UDFs are usable today. Cloud deployments must wait for a GA announcement or use the existing executable-UDF approach.
See also: How to write a ClickHouse WebAssembly UDF — practical howto covering syntax, compilation, and loading
| Version | Release Date | Key WASM Changes |
|---|---|---|
| v26.3 LTS | 2026-03-26 | Initial release. ROW_DIRECT + BUFFERED_V1 ABIs, Wasmtime + WasmEdge runtimes, system.webassembly_modules |
| v26.4 | 2026-04-30 | No new features; Wasmtime bumped to v43.0.1 |
| v26.5 | 2026-05-21 | DETERMINISTIC keyword, numeric widening coercions, system.functions integration, segfault fix, LIKE support in DELETE |
| v26.6 | 2026-06-25 | AssemblyScript ABI, Buffers serialization, per-UDF fuel setting, Materialized View bug fix, WasmEdge startup overhead fix (−8.3% wall time), Wasmtime v45 |
Enabling the feature (server config):
<allow_experimental_webassembly_udf>true</allow_experimental_webassembly_udf>
<webassembly_udf_engine>wasmtime</webassembly_udf_engine>Two bundled runtimes are available:
- Wasmtime (default, recommended) — Bytecode Alliance runtime, v45.0.1 in v26.6. Used for production workloads.
- WasmEdge — Alternative runtime. Had a severe startup overhead bug (added ~15% wall time to every
clickhouseinvocation even without WASM UDFs) fixed in v26.6 (PR #107869).
Three calling conventions are available today; a fourth is in review:
| ABI | Scope | Types Supported | Overhead | Use Case |
|---|---|---|---|---|
ROW_DIRECT |
Row-by-row | Primitive numerics only (Int32/64, UInt32/64, Float32/64, Int128/UInt128) | Minimal — direct WASM call | Math-heavy scalar functions |
BUFFERED_V1 |
Block-based | All ClickHouse types including String, Array, etc. | Serialization round-trip per block (MsgPack default; also JSONEachRow, CSV, TSV, RowBinary, Buffers) | String processing, complex types |
ASSEMBLYSCRIPT |
Row-by-row | Numerics + String (UTF-8↔UTF-16 conversion) | Per-row | AssemblyScript-compiled modules |
COLUMNAR_V1 |
Block-based | TBD | Lowest — bypasses RowBinary | High-throughput analytics (PR #104424, not yet merged) |
-- Step 1: Load the .wasm binary into the server
INSERT INTO system.webassembly_modules (name, code)
SELECT 'my_module', file('/path/to/module.wasm');
-- Step 2: Create a function referencing the module
CREATE [OR REPLACE] FUNCTION function_name
LANGUAGE WASM
FROM 'module_name' [:: 'source_function_name']
ARGUMENTS (arg1 Int32, arg2 Float64)
RETURNS Float64
[ABI ROW_DIRECT | BUFFERED_V1 | ASSEMBLYSCRIPT]
[DETERMINISTIC]
[SETTINGS webassembly_udf_max_fuel = 1000000];
-- Inspect loaded modules
SELECT name, hash FROM system.webassembly_modules;
-- Remove a module (v26.5+: supports LIKE pattern)
DELETE FROM system.webassembly_modules WHERE name LIKE 'my_%';Functions the WASM guest can call back into ClickHouse:
| Function | Description |
|---|---|
clickhouse_server_version() |
Returns version as integer |
clickhouse_throw(ptr, size) |
Raise an error |
clickhouse_log(ptr, size) |
Write to ClickHouse log (added v26.3, log-level aware) |
clickhouse_random(ptr, size) |
Cryptographic random bytes |
env.abort(...) |
AssemblyScript trap handler |
Not yet available: shared cache API (clickhouse_cache_get/set) — feature request #99645 is open.
Per-UDF and per-query settings:
| Setting | Description | Default |
|---|---|---|
webassembly_udf_max_fuel |
Instruction budget (count × 1024); 0 = unlimited | Enabled |
webassembly_udf_max_memory |
Memory limit in bytes | — |
webassembly_udf_max_input_block_size |
Max rows per block (BUFFERED_V1) | — |
webassembly_udf_max_instances |
Parallel WASM instances | — |
webassembly_udf_enable_fuel |
Toggle fuel accounting (persisted per-UDF since v26.6) | true |
Note: Disabling webassembly_udf_enable_fuel is recommended for trusted modules — fuel accounting incurs measurable Wasmtime overhead.
ProfileEvents tracked per query:
WasmSerializationMicrosecondsWasmDeserializationMicrosecondsWasmGuestExecuteMicrosecondsWasmTotalExecuteMicroseconds
Since v26.5, WASM UDFs appear in system.functions with correct origin, syntax, arguments, and returned_value fields — enabling standard introspection and query analyzer integration.
| PR | Title | State | Merged | Author |
|---|---|---|---|---|
| #80291 | Wasm udf (initial draft) | MERGED | 2026-02-26 | lioshik |
| #88747 | Support WebAssembly UDFs (main implementation) | MERGED | 2026-02-26 | vdimir |
| #98582 | Followup for wasm udfs — cancellation, settings to query level | MERGED | 2026-03-05 | vdimir |
| #98859 | Update docs/wasm_udf.md | MERGED | 2026-03-05 | vdimir |
| #99373 | WASM UDF improvements — numeric coercion, C++ exceptions, clickhouse_log |
MERGED | 2026-03-18 | bacek |
| PR | Title | Merged | Author |
|---|---|---|---|
| #100005 | Add DETERMINISTIC keyword | v26.5 | bacek |
| #100435 | Widening coercions: int→float, i32→i64, f32→f64 | v26.5 | bacek |
| #101053 | Show WASM UDFs in system.functions with arguments and return type | v26.5 | bacek |
| #104397 | Support LIKE in DELETE FROM system.webassembly_modules | v26.5 | vdimir |
| #103101 | Fix segfault during WASM module delete | v26.5 | JosephRedfern |
| PR | Title | Merged | Author |
|---|---|---|---|
| #104606 | Implement AssemblyScript WASM UDF ABI | v26.6 | vdimir |
| #105296 | Add note about distributed cluster to WASM UDF docs | v26.6 | vdimir |
| #105574 | Support Buffers serialization format + per-UDF fuel setting | v26.6 | antonio2368 |
| #106161 | Fix WASM UDFs in Materialized Views | v26.6 | niyue |
| #107869 | Bump WasmEdge contrib — removes costly per-process startup hash | v26.6 | Algunenano |
| #106836 | Use Wasmtime v45.0.1 | v26.6 | thevar1able |
| PR | Title | Status |
|---|---|---|
| #104424 | COLUMNAR_V1 wire format — bypass RowBinary for high-throughput | Open (active review; Nullable handling + bounds issues flagged) |
| #103487 | Fix Cranelift stack overflow on aarch64-apple-darwin | Open (unfixed) |
| #105131 | Drivers for executable UDFs (Alexey Milovidov) | Open — related roadmap item, not WASM-specific |
- Not available on ClickHouse Cloud —
CloudNotSupportedBadgeon docs page. Private preview available via support contact; no GA timeline announced. - Requires server-level config flag —
allow_experimental_webassembly_udf = 1; cannot be enabled per-query. wasm32-unknown-unknownonly — freestanding WASM only; no OS syscalls, no standard library, nowasm64.- No ON CLUSTER INSERT — loading a module to a cluster requires per-node fanout; there is no single
INSERT INTO system.webassembly_modules ON CLUSTERstatement. Documented workaround: setinternal_replication = falseon the Distributed table, or script manual uploads. - aarch64-apple-darwin Cranelift stack overflow — PR #103487 open, unfixed in v26.5/v26.6.
- WASM UDFs in Materialized Views require v26.6+ (or
enable_analyzer = true) — On clusters running < v26.6 withenable_analyzer = false(the old query analyzer),CREATE MATERIALIZED VIEWvalidation routes through the old interpreter path, which rejects WASM UDFs as non-SQL UDFs with:
Root cause: the oldDB::Exception: The function 'fn(...)' is not a SQL defined function and is not supported when 'enable_analyzer' is set to false. (UNSUPPORTED_METHOD)CREATE MATERIALIZED VIEWvalidation path classified WASM functions as unrecognized non-SQL UDFs. Fixed in v26.6.1.463 (PR #106161, issue #99789). Workaround on older versions: setenable_analyzer = 1at query or session level before creating the MV.
ROW_DIRECT: no String, no Array, no complex types.ASSEMBLYSCRIPT: no custom classes.BUFFERED_V1: serialization overhead on every block; COLUMNAR_V1 aims to fix this but is not merged.
- No shared cache — Issue #99645:
clickhouse_cache_get/set/get_or_setkeyed by (UDF name, module hash, user). No PR yet. - No ZooKeeper/Keeper interaction — Issue #106841: cannot implement shared counters or auto-increment generators. No PR.
| Issue | Status | Fix Version | Description |
|---|---|---|---|
| #99789 | Fixed | v26.6.1.463 | WASM UDFs failed in CREATE MATERIALIZED VIEW when enable_analyzer = false; old interpreter path classified them as unsupported non-SQL UDFs |
| #103100 | OPEN | Partial (v26.5) | Segfault when unloading WASM modules via DELETE FROM system.webassembly_modules. The WHERE 1=1 case was patched in v26.5 (PR #103101), but the issue remains open — the server still crashes on macOS/aarch64 (Apple Silicon) with other DELETE patterns. Root cause: null pointer dereference in std::operator== during module cleanup. Reported on v26.4. |
| #100049 | Fixed | v26.3+ | BUFFERED_V1 + RowBinary + type coercion produced wrong results |
| — | Fixed | v26.6 | WasmEdge startup hash computation added ~15% overhead to all clickhouse invocations |
- GitHub: https://github.com/ClickHouse/clickhouse-wasm-udf-rs
- Crate:
clickhouse-wasm-udfon crates.io - Uses
#[clickhouse_udf]attribute macro - Supports serde-serializable types: numbers, strings, Vec, HashMap
- Target:
wasm32-unknown-unknown
Minimal Rust example:
use clickhouse_wasm_udf::clickhouse_udf;
#[clickhouse_udf]
pub fn add_two(x: i32) -> i32 {
x + 2
}- chgeos (Vasily Chekalkin, main WASM UDF contributor): PostGIS-compatible spatial functions (
ST_Intersects,ST_Buffer,ST_Within, etc.) as a WASM module backed by GEOS 3.12+. This is the most prominent real-world use case and serves as the reference implementation for BUFFERED_V1.
AssemblyScript users can write typed TypeScript-like code and compile to WASM targeting the new ASSEMBLYSCRIPT ABI. A browser playground is bundled in the v26.6 docs.
| Database | WASM UDF Approach | Status |
|---|---|---|
| ClickHouse | WASM modules loaded server-side; runs inside the CH process in a sandboxed WASM runtime (Wasmtime or WasmEdge); three ABIs; resource budgets | Experimental since v26.3 (2026-03); self-managed only |
| DuckDB | DuckDB-Wasm compiles the database engine itself to WASM for browser-side analytics — a different concept. DuckDB supports C++ native UDFs via API but has no WASM-as-UDF model inside the server | Mature for browser; no server-side WASM UDF |
| PostgreSQL | Extension ecosystem only: wasmer-postgres, pg_extism (Extism PDK), pl/wasm. Not part of core Postgres |
Mature ecosystem, not core |
| SingleStore | WASM UDF with rich host API; cited by the ClickHouse community in 2024 as the bar ClickHouse should match | GA, more mature API surface |
Key architectural insight: ClickHouse's model is the closest to SingleStore's — WASM modules run inside the server process in a sandboxed environment with well-defined host ↔ guest ABIs. DuckDB's WASM story is orthogonal (runs the engine in a browser). PostgreSQL's ecosystem is extension-only and not core.
- 2026 roadmap (issue #93288): WASM UDFs were explicitly listed and are now checked off as delivered.
- COLUMNAR_V1 ABI (PR #104424): The next major performance milestone — direct columnar wire format bypassing RowBinary for high-throughput analytical workloads like spatial analytics. In active review with correctness issues to resolve.
- Shared cache host API (issue #99645): Needed for stateful patterns; no PR yet.
- ZooKeeper/Keeper interaction (issue #106841): Needed for cross-instance coordination; no PR yet.
- Cloud GA: No public timeline. Current signal: "private preview" available by contacting ClickHouse Support.
WASM UDFs are viable today for:
- Scalar math-heavy functions with primitive types → use
ROW_DIRECT(zero serialization overhead) - String processing, complex types → use
BUFFERED_V1with Buffers format (v26.6+) - Spatial analytics → chgeos project demonstrates production-grade patterns
Key caveats to test:
- Fuel budget overhead — benchmark
webassembly_udf_enable_fuel = truevsfalsefor your workload - Module distribution across cluster nodes — must be scripted; no
ON CLUSTERsupport - COLUMNAR_V1 performance improvements are not yet available; if throughput is critical, wait for that PR to merge
Not GA. Request private preview access via ClickHouse Support. Plan for executable UDFs as the fallback — they are Cloud-compatible and support any language.
| Language | ABI | Notes |
|---|---|---|
| Rust | BUFFERED_V1 or ROW_DIRECT | Official SDK, best ecosystem |
| AssemblyScript | ASSEMBLYSCRIPT | Easiest for TypeScript developers; browser playground available |
| C/C++ | ROW_DIRECT or BUFFERED_V1 | Possible but no official helper; must compile with emcc or clang --target wasm32 |
| Go | BUFFERED_V1 | Possible via TinyGo; not officially tested |
| Resource | URL |
|---|---|
| Official WASM UDF docs | https://clickhouse.com/docs/sql-reference/functions/wasm_udf |
| v26.3 release blog | https://clickhouse.com/blog/clickhouse-release-26-03 |
| Rust SDK (GitHub) | https://github.com/ClickHouse/clickhouse-wasm-udf-rs |
| Rust crate | https://crates.io/crates/clickhouse-wasm-udf |
| chgeos spatial WASM module | https://github.com/bacek/chgeos |
| Main implementation PR | ClickHouse/ClickHouse#88747 |
| COLUMNAR_V1 ABI PR (open) | ClickHouse/ClickHouse#104424 |
| Shared cache feature request | ClickHouse/ClickHouse#99645 |
| Original feature request (closed) | ClickHouse/ClickHouse#36892 |
| 2026 Roadmap | ClickHouse/ClickHouse#93288 |