SQL Workspace
CH-UI provides a ClickHouse-first SQL workspace with a multi-tab editor, schema explorer, autocomplete, query streaming, result filtering and sorting, automatic query history, error navigation, data upload, and saved queries.
Overview
Section titled “Overview”The rail on the left groups the product into six areas:
| Group | Pages |
|---|---|
| Query | The SQL workspace: editor tabs and the schema explorer |
| Explore | Saved Queries, Brain |
| Visualize | Dashboards, Telemetry |
| Build | Models, Pipelines, Schedules |
| Operate | Cluster Health, Query Insights, Cost Center, Governance |
| Settings | Admin, License |
Picking a group opens its list in the context panel next to the rail. Pages are full screens with their own URL, and pages with several parts (Admin, Governance, Telemetry, the Operate pages) list them as sections in that panel instead of showing tab strips. Tabs are kept for the query workspace, where holding two things open side by side is the point: it has two split groups (left/right), and each tab is a query editor or a table/database browser.
The explorer panel can be resized by dragging and collapsed with ⌘B / Ctrl+B.
Multi-Tab Editor
Section titled “Multi-Tab Editor”The SQL editor uses CodeMirror 6 with ClickHouse syntax highlighting and schema-aware autocomplete.
Tab Types
Section titled “Tab Types”| Type | Behavior |
|---|---|
| Query | Multi-instance, supports dirty tracking, SQL persisted in localStorage |
| Table/Database | Context-specific browsing with data preview |
Persistence
Section titled “Persistence”Tabs are saved to localStorage (ch-ui-tabs) with auto-save via microtask debounce. Tab state survives browser refreshes and includes:
- Tab ID, type, and name
- SQL content and dirty flag
- Saved query reference (if linked)
- Split group assignment (left/right)
Split View
Section titled “Split View”Open up to two editor groups side by side. Tabs can be moved between groups. Duplicate detection prevents opening the same saved query twice: opening it again brings the existing tab forward and, if the tab has no unsaved edits, reloads the stored SQL and parameter defaults into it.
Running statements
Section titled “Running statements”| Shortcut | Runs |
|---|---|
| ⌘↵ / Ctrl+Enter | The selection; with no selection, the statement under the cursor when the editor holds several (the active statement is shaded); otherwise the whole editor |
| ⇧⌘↵ / Shift+Ctrl+Enter | Every statement in the editor (or in the selection), one after another, stopping at the first error. The result pane keeps the last result |
Statements are split on ; outside strings, quoted identifiers, heredocs and comments. The Run button, Explain, the query plan, sampling and the cost estimate all use the same text as ⌘↵, with the current parameter values.
Formatting
Section titled “Formatting”Format (Shift+Alt+F) asks ClickHouse to format the SQL with formatQuery() (ClickHouse 23.10 and later), so the result always means the same as the input. ClickHouse’s formatter drops comments; CH-UI tells you when that happens, and ⌘Z brings them back. On older servers, or when ClickHouse cannot parse the text, a built-in formatter puts clauses on their own lines and upper-cases a few unambiguous keywords, without touching strings, comments or identifiers.
Cost estimate
Section titled “Cost estimate”After you stop typing for a few seconds, the toolbar shows an EXPLAIN ESTIMATE of the rows, parts and marks the query would read. It runs only when the editor holds a single SELECT (or WITH) whose parameters all have values, and it goes through the same governance guardrails as a run.
Query Execution
Section titled “Query Execution”Standard Execution
Section titled “Standard Execution”POST /api/query/run{ "query": "SELECT * FROM system.tables LIMIT 20", "timeout": 30, "maxResultRows": 1000}| Parameter | Description | Default | Max |
|---|---|---|---|
query |
SQL to execute | Required | None |
timeout |
Seconds before timeout | 30 | 300 (5 min) |
maxResultRows |
Server-side row limit sent to ClickHouse | 1000 | None |
Query Streaming
Section titled “Query Streaming”For large result sets, use SSE-based streaming:
POST /api/query/streamReturns chunked NDJSON with message types:
| Type | Content |
|---|---|
meta |
Column names and types |
chunk |
Data rows (with sequence number) |
progress |
Live progress: elapsed time, rows and bytes read, percent complete |
done |
Statistics (rows and bytes read, rows written for writes), total row count, and the query_id |
error |
Error message |
Stream limit: 1,000,000 rows.
Live progress and cancel
Section titled “Live progress and cancel”While a query runs, the result panel shows elapsed time, percent complete, rows and bytes read, and read throughput, the same numbers ClickHouse’s own /play reports. Progress is sampled from system.processes every 300 ms. A user without SELECT on system.processes simply gets no live readout, never an error. The readout stays after the query ends, so a query too short to report progress still shows what it read.
Statements that work while the connection is open report progress too (INSERT ... SELECT, OPTIMIZE TABLE ... FINAL); mutations run in the background, so their readout covers only the statement itself.
Cancelling a query, or closing its tab, stops it on ClickHouse: every streamed query carries a query_id, and an abandoned stream is killed with KILL QUERY instead of running to completion unattended.
EXPLAIN & Profiling
Section titled “EXPLAIN & Profiling”| Endpoint | Purpose |
|---|---|
POST /api/query/explain |
Raw EXPLAIN output (a statement that already starts with EXPLAIN runs as written) |
POST /api/query/plan |
Parsed query plan as tree (tries EXPLAIN PLAN, falls back to EXPLAIN AST, then generic) |
POST /api/query/profile |
system.query_log metrics of a streamed run, looked up by the query_id from its done message (duration, read rows/bytes, memory). query_log is flushed asynchronously, so the lookup retries once |
POST /api/query/sample |
First N rows per shard (default 25, max 500), falls back to global LIMIT |
POST /api/query/estimate |
EXPLAIN ESTIMATE for a single SELECT |
Explain, plan, sample and estimate accept the same params object as a run.
Working with Results
Section titled “Working with Results”Sorting & Filtering
Section titled “Sorting & Filtering”Click any column header to sort, or use the per-column filter popovers (type-aware operators: contains, equals, comparisons, NULL checks). Active filters show as removable chips above the grid.
CH-UI picks the cheapest correct strategy automatically:
- Complete result in memory (under the row limit and ≤ 50k rows): sorting and filtering happen instantly client-side.
- Truncated or huge results: the original query is re-run on the server wrapped as
SELECT * FROM (your query) WHERE … ORDER BY …, BigQuery-style, so you get the true top-N over the full dataset instead of a misleading sort of the loaded page. The footer marks these views as server-filtered.
Exports always reflect the filtered/sorted view you’re looking at. The feature can be toggled off in the result footer.
Rows, cells and exports
Section titled “Rows, cells and exports”Rows appear in the grid as they stream in. A statement that returns no columns (DDL, INSERT) shows Statement executed with the rows written, when ClickHouse reports them, and the elapsed time.
Click a cell to focus it, then press Enter (or double-click the cell) to open the cell inspector, which pretty-prints JSON, arrays and maps and copies the value. ⌘C / Ctrl+C copies the focused cell; arrow keys move between cells.
Exports and copies come as CSV, TSV, JSON, JSONCompact, JSONLines, Markdown, SQL INSERT and XML. CSV and TSV prefix text that starts with =, +, - or @ with ' so spreadsheets do not run it as a formula; numbers such as -5 are left as they are. The SQL INSERT export quotes values by column type (a String column holding 01234 stays a string) and writes arrays, maps and tuples as ClickHouse literals.
Explorer shortcuts
Section titled “Explorer shortcuts”Right-click a table in the explorer for Preview 100 Rows (opens a query tab), Show DDL (the SHOW CREATE TABLE output in a side panel, with copy), Copy Qualified Name and Copy Column List. Refreshing the schema also refreshes the editor’s autocomplete.
Query History
Section titled “Query History”Every query you run from the editor is recorded automatically: full SQL, success/error/cancelled status, duration, and row count. Open it with the History button in the editor toolbar:
- Search and filter (all / success / errors)
- Open any entry in a new tab, copy it, or replace the current editor contents
- Per user, per connection: the last 500 runs are kept; clear it anytime. For SSO people this is per person (by email), not per shared service account, and includes queries their MCP clients ran
Credential-bearing statements (IDENTIFIED BY, s3(...) keys) are redacted before storage, and history lives in CH-UI’s local SQLite, so it never leaves your server.
GET /api/query-history?search=&status=&limit=50&offset=0DELETE /api/query-history/{id}DELETE /api/query-history # clear all (current user + connection)Error Navigation
Section titled “Error Navigation”Failed queries show a structured error card instead of a raw dump: the ClickHouse error name and code (SYNTAX_ERROR · 62), a cleaned message, an actionable hint for common codes (unknown table/column, memory limit, timeouts…), and the full raw error in a collapsible.
When ClickHouse reports a position, a “Go to line N, col N” button jumps the editor cursor straight to the failing token and selects it. It works with both pre- and post-25.2 ClickHouse error formats.
Schema Explorer
Section titled “Schema Explorer”The left sidebar provides a navigable schema tree.
Explorer Endpoints
Section titled “Explorer Endpoints”| Endpoint | Purpose |
|---|---|
GET /api/query/databases |
List all databases |
GET /api/query/tables?database=db |
List tables in a database |
GET /api/query/columns?database=db&table=tbl |
List columns with types |
GET /api/query/data-types |
List all ClickHouse data types |
GET /api/query/clusters |
List cluster names |
Table Browser
Section titled “Table Browser”Click a table in the explorer to open a table tab showing:
- Column list with types
- Data preview (paginated, default 100 rows, max 1000)
- Host info and table metadata
Autocomplete
Section titled “Autocomplete”The editor provides schema-aware completions using database, table, and column names from the explorer endpoints.
Open a table from the editor
Section titled “Open a table from the editor”Hold ⌘ (macOS) or Ctrl and click a database or table name in the editor to open its tab. Names you can open have a dotted underline, and hovering one shows what it opens. Holding the key turns the underline solid. In otel.otel_logs, clicking otel opens the database and clicking otel_logs opens the table; backtick and double-quote quoting both work. A table name without a database (FROM otel_logs) is a link only when exactly one database has a table with that name.
Schema Operations
Section titled “Schema Operations”Admin-only operations for managing databases and tables.
Create Database
Section titled “Create Database”POST /api/query/schema/database{ "name": "analytics", "engine": "Atomic", "on_cluster": "default", "if_not_exists": true}Drop Database
Section titled “Drop Database”POST /api/query/schema/database/drop{ "name": "analytics", "if_exists": true, "sync": true}Create Table
Section titled “Create Table”POST /api/query/schema/table{ "database": "analytics", "name": "events", "engine": "MergeTree", "columns": [ { "name": "id", "type": "UInt64" }, { "name": "timestamp", "type": "DateTime" }, { "name": "event_type", "type": "String" } ], "order_by": "(timestamp)", "partition_by": "toYYYYMM(timestamp)", "if_not_exists": true}Drop Table
Section titled “Drop Table”POST /api/query/schema/table/drop{ "database": "analytics", "name": "events", "if_exists": true}Data Upload
Section titled “Data Upload”Upload CSV, JSON, JSONL, or Parquet files into ClickHouse tables. Max file size: 25 MB.
Two-Step Process
Section titled “Two-Step Process”Step 1: Discover schema
POST /api/query/upload/discoverContent-Type: multipart/form-dataUpload the file. The backend detects the format, parses all rows, infers column types, and returns a preview (20 rows) with the inferred schema.
Type inference order: Bool > Int64 > Float64 > DateTime > Date > String. Nullable wrapper added if any null values found.
Step 2: Ingest data
POST /api/query/upload/ingestContent-Type: multipart/form-dataConfirm the target database, table, and column mapping. Optionally create the table with a custom engine, ORDER BY, PARTITION BY, TTL, etc.
Data is inserted via JSONEachRow in 500-row batches with a 90-second timeout per batch.
Response
Section titled “Response”{ "success": true, "database": "analytics", "table": "events", "rows_inserted": 15000, "created_table": true, "commands": { "create_table": "CREATE TABLE ...", "insert": "INSERT INTO ... FORMAT JSONEachRow" }}Saved Queries
Section titled “Saved Queries”Save, duplicate, update, and delete SQL snippets for reuse.
# ListGET /api/saved-queries
# CreatePOST /api/saved-queries{ "name": "Daily active users", "query": "SELECT count(DISTINCT user_id) ..." }
# Update (also used to rename: send just the name)PUT /api/saved-queries/{id}{ "name": "Updated name", "query": "..." }
# Duplicate (optionally pass a name for the copy)POST /api/saved-queries/{id}/duplicate{ "name": "My copy" } # optional; defaults to "<original> (copy)"
# DeleteDELETE /api/saved-queries/{id}Opening a saved query in the editor links the tab to the saved query ID. Changes are tracked via a dirty flag. Rename a saved query from the Saved Queries list (right-click → Rename) or by sending name to the PUT endpoint.
The Saved Queries page
Section titled “The Saved Queries page”Explore → Saved Queries lists your library like files in a folder: one search box, an All / Verified switch, and a table with sortable Name and Updated columns (click a header to flip the order). Updated shows relative time, with the full date on hover.
- Click or double-click a row to open the query in the editor.
- Hover a row for Open and a ⋯ menu; right-click opens the same menu.
- The details sheet shows the SQL, description and dates, with the actions in its footer.
Verified queries
Section titled “Verified queries”Mark a saved query as verified once someone has checked that the SQL is correct. The flag shows in the list and filters it with the Verified switch. AI clients connected through the MCP server see it too, and are told to prefer a verified query over writing new SQL for the same question. Verification is a review mark, not a permission: unverified queries still run.
PUT /api/saved-queries/{id}{ "verified": true }Query Parameters
Section titled “Query Parameters”Query parameters in the editor are part of the free core. Only the saved-query Run API below needs a Pro license.
Use ClickHouse’s native bind-parameter syntax, {name:Type}, directly in your SQL. CH-UI detects parameters automatically (ignoring text inside strings and comments) and counts them on the Parameters button in the editor toolbar. The button opens a popover with an input for each one, and Run opens it on the first parameter that still has no value, so you never hit an “unbound parameter” error. Values are kept per tab, across tab switches and reloads.
SELECT *FROM eventsWHERE user_id = {user_id:UInt64} AND created_at >= {since:DateTime}LIMIT {limit:UInt32}Values are bound safely by ClickHouse (passed as param_<name> URL params), never string-interpolated into the SQL.
Default values
Section titled “Default values”When you save a parameterized query, the current parameter values are stored as defaults (in the parameters field). Opening the saved query fills the inputs with them. Saving again with a field left empty keeps the stored default for it instead of erasing it:
POST /api/saved-queries{ "name": "Events for user", "query": "SELECT * FROM events WHERE user_id = {user_id:UInt64}", "parameters": { "user_id": "1001" }}Running a saved query via the API
Section titled “Running a saved query via the API”Execute a saved query, with parameters, and get the result back as JSON. Stored defaults are merged with any params you supply (your values win). Pro only (an enterprise edition license unlocks it too).
POST /api/saved-queries/{id}/run{ "params": { "user_id": "42", "since": "2026-01-01 00:00:00", "limit": "100" }, "maxResultRows": 1000, "timeout": 30}The response matches the standard query-execution shape (data, meta, statistics, rows, elapsed_ms). Without an active Pro license this endpoint returns 402 Payment Required, including during the grace period after expiry.
Command Palette
Section titled “Command Palette”Press ⌘K (macOS) or Ctrl+K anywhere in the workspace to open the command palette:
- Navigate: fuzzy-search pages, tables, recent tabs, and (Pro) saved queries, dashboards, models, pipelines, Brain chats, and telemetry tabs
- Run actions: new query (
⌘⇧N), new dashboard / model / pipeline / Brain chat, toggle theme, sign out - Scope with prefixes (Pro):
>actions,t:tables,q:saved queries,d:dashboards,m:models,p:pipelines,b:Brain chats,tel:telemetry,?help - Ask Brain (Pro): type a question and hit Enter to seed a Brain chat with the prompt
Display Controls
Section titled “Display Controls”Max Result Rows
Section titled “Max Result Rows”Controls how many rows the UI requests from the server. Stored in localStorage (ch-ui-max-result-rows).
- Default: 1000
- Minimum: 1
- Sent as
maxResultRowsparameter on query execution
Number Formatting
Section titled “Number Formatting”Toggle locale-aware number formatting (thousands separators). Stored in localStorage (ch-ui-format-numbers), enabled by default.
Guardrails Integration
Section titled “Guardrails Integration”All query endpoints run governance guardrails before execution. If a policy with block enforcement mode matches, the query is rejected with a 403. See Governance for details.
Safety Defaults
Section titled “Safety Defaults”| Control | Value |
|---|---|
| Query timeout | 30s default, 5 min max |
| Stream row limit | 1,000,000 |
| Upload file size | 25 MB |
| Upload batch size | 500 rows |
| Schema operations | Admin-only |
| Brain-generated SQL | LIMIT 100 default |
