Export

Export streams straight to disk — even a whole script

Altinity engineering·June 2026·5 min read

The result grid caps rows so your browser survives. Export doesn't use the grid at all.

A SQL console has a tension baked in. To stay responsive it caps how many rows it shows — we default to 500. But sometimes you don't want to look at the data, you want to take it: dump a table to Parquet, hand a CSV to a colleague, feed a pipeline. Capping is exactly wrong for that, and buffering a few million rows in a browser tab to assemble a download is how you OOM the tab.

So Export doesn't touch the grid. Click Export, pick a file, and the browser streams ClickHouse's response body straight to disk — resp.body piped into a file opened through the File System Access API. Nothing is buffered in the page; memory stays flat whether the result is a thousand rows or a billion. It runs uncapped — a file has no scroll performance to protect.

The format is the query

You don't choose a format from a menu — you write it. End the query with FORMAT Parquet and you get a .parquet; CSV.csv; JSONEachRow.jsonl; Arrow, ORC, Avro, Native, even SQLInsert — ClickHouse's whole format family, streamed verbatim, with the right extension picked for you. No FORMAT clause and you get TabSeparatedWithNames, the .tsv that opens cleanly in Excel or pandas. The console isn't re-serializing anything — ClickHouse formats the bytes, they flow to the file.

SELECT * FROM github.github_events FORMAT Parquet
→ github_events.parquet     streaming…  4.2 GB · 12s · Cancel

Even a whole script

Here's the part I like. Point Export at a multi-statement script — the same ;-separated scripts multiquery runs — and instead of a file picker you get a directory picker. Each statement runs in order in one shared session, and every row-returning statement streams uncapped to its own file, numbered to match the log:

etl-nightly/
  001-raw-events.parquet     4.2 GB
  002-daily-rollup.csv        18 MB
  003-top-accounts.tsv       2.1 MB

Effectful statements — CREATE, INSERT — run for effect and just log OK. Because it's one session, a CREATE TEMPORARY TABLE in step 1 is still there for the SELECT in step 3. A live log shows status, file, bytes and elapsed per statement — metadata only, never the rows, so exporting a script that emits gigabytes doesn't cost the tab a thing. A nightly-extract that used to be a shell loop of clickhouse-client --queries-file redirects is now: paste, Export, pick a folder.

It won't hand you a broken file

Streaming has a nasty failure mode. Once the HTTP response has started, its 200 is locked in — so a ClickHouse error partway through can't come back as a status code. It arrives as text, in the middle of your data. Pipe that straight to disk and the error becomes a corrupt row in your Parquet file that you discover much later.

Export guards against it. ClickHouse tags a mid-stream failure — an X-ClickHouse-Exception-Tag header and a trailing exception frame — and the writer holds back the tail of the stream until it can confirm the end is clean, excising that frame before it ever reaches disk. A failed export says "Export incomplete"; it never bakes the error into your file. Cancel works the same way: it aborts the stream and issues its own KILL QUERY, independent of whatever the result grid is doing.

It's Chromium-only for now — the File System Access API isn't everywhere yet, and the button tells you when it can't run. If you're on Chrome, paste a SELECT … FORMAT Parquet on the demo and hit Export, or point it at a script and watch a folder fill up.

More from the blog:
Run a whole SQL script → A query library you can commit → The SQL console with no backend →