The fully open-source DuckDB workbench that runs in your browser. SQL editor, notebooks, charts, AI assistant. No install, no signup, no backend — your data never leaves the tab.
See the codeThe fully open-source DuckDB workbench that runs in your browser.
No install, no signup, no backend. Your data never leaves the tab.
Try it now → duckui.com · Docs · Contributing

Or run it yourself:
docker run -p 5522:5522 ghcr.io/caioricciuti/duck-ui:latest
Open http://localhost:5522. That's the whole setup.
⌘K finds any of them, plus tables, saved queries and recent queries.EXPLAIN ANALYZE viewer. ⌘Enter runs the statement under the cursor, ⌘Shift+Enter the whole tab. A failed query is underlined where DuckDB points.sql() returning pandas DataFrames) and markdown cells, each with its own result and chart.$name placeholders get an input bar, in SQL tabs and notebooks..duckdb files. Drop a file on the explorer, paste a URL, or read straight from S3, GCS, Azure, R2 or MinIO. Local folders through the File System Access API.?load=ducklake: links, or plain ATTACH SQL.The whole app is built on Svelte 5 and CodeMirror 6. The startup bundle is about 350 kB of JavaScript; everything else, including the AI runtime, loads the first time you use it.
duckdb -ui?Both are good tools. The differences that matter:
| Duck-UI | Official DuckDB UI | |
|---|---|---|
| Frontend source | MIT, all of it in this repo | Closed source |
| Runs from | Any static host, Docker, or a URL | Local duckdb process, UI assets loaded remotely |
| Works offline / air-gapped | Yes | No |
| Self-host / embed / kiosk | Yes | No |
| AI assistant | Bring your own provider, or fully local WebLLM | MotherDuck account |
| External DuckDB servers | Yes (httpserver) | Local + MotherDuck only |
If you just want a quick local UI and don't care about any of that, the official one is fine. Duck-UI is for when the answers to "can I host it, embed it, extend it, run it offline, and read the code" need to be yes.
Any hosted dataset can become a one-click, runnable analysis. Add ?load= (and optionally &sql=) to the app URL. Try this one:
https://duckui.com/?load=https://blobs.duckdb.org/stations.parquet
&sql=SELECT country, count(*) AS stations FROM stations GROUP BY country ORDER BY stations DESC
Openers see exactly what will load and what will run, confirm once, and get a live editor over your data. Nothing touches a server. Parquet, CSV, JSON, .duckdb files, and ducklake: catalogs are supported. The Share dialog generates the link and a README badge for you:
The data host needs CORS enabled, see hosting your data for a free R2/GitHub Pages setup. More examples in the gallery.
The cli/ launcher serves the built app on 127.0.0.1 and opens it with your local files already queued to load, using the same ?load= links as above:
bun run build # once; the CLI serves dist/
node cli/duck-ui.js sales.csv events.parquet # or: npx duck-ui ... once published
node cli/duck-ui.js db.duckdb --port 6000 --no-open --sql "SHOW ALL TABLES"
CSV/TSV, Parquet, JSON/NDJSON and .duckdb files are supported. Only dist/ and the exact files you name are served (no directory listing or traversal), and requests must come from the local machine. The package is "private": true, so npx duck-ui works only after that flag is removed and the package (with a built dist/) is published.
Duck-UI doubles as a zero-backend data-publishing appliance. Drop a manifest next to the build, deploy to GitHub Pages, and visitors get a read-only SQL explorer for your data:
{
"ui": { "kiosk": true },
"databases": [
{ "name": "My Dataset", "file": "ducklake:https://pub-xxxx.r2.dev/catalog.ducklake" }
]
}
Bundled .db files, remote files over HTTPS, S3, and DuckLake catalogs are all supported. Remote sources are attached in place and read-only by default. See public/databases/README.md for the full manifest format and deploy steps.
Runtime environment variables (Docker):
| Variable | Description | Default |
|---|---|---|
DUCK_UI_EXTERNAL_CONNECTION_NAME | Name for a pre-configured external connection | "" |
DUCK_UI_EXTERNAL_HOST | Host URL for external DuckDB (may include a path) | "" |
DUCK_UI_EXTERNAL_PORT | Port for external DuckDB (NAME, HOST and PORT must all be set or the connection is skipped) | null |
DUCK_UI_EXTERNAL_API_KEY | API key sent as X-API-Key (takes priority over user/pass) | "" |
DUCK_UI_EXTERNAL_USER / DUCK_UI_EXTERNAL_PASS | Basic auth credentials | "" |
DUCK_UI_EXTERNAL_DATABASE_NAME | Database name for the external connection | "" |
DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS | Allow unsigned DuckDB extensions | false |
DUCK_UI_DUCKDB_WASM_USE_CDN | Load DuckDB WASM from CDN | false |
DUCK_UI_DUCKDB_WASM_BASE_URL | Custom CDN base URL (the origin is added to the CSP automatically at container start) | auto jsDelivr |
DUCK_UI_PYODIDE_BASE_URL | Where Python notebook cells load Pyodide from: a URL or same-origin path (e.g. /pyodide/) of a Pyodide 0.29.5 "full" distribution folder. A cross-origin URL's origin is added to the CSP at container start | https://cdn.jsdelivr.net/pyodide/v0.29.5/full/ |
DUCK_UI_STUN_URLS | Comma-separated STUN servers for live sessions. Set it empty for a LAN-only deployment with no STUN at all | Google's public STUN |
DUCK_UI_TURN_URLS | Comma-separated TURN relays for live sessions between networks that cannot connect directly | none |
DUCK_UI_TURN_USERNAME / DUCK_UI_TURN_CREDENTIAL | TURN credentials. They reach every browser that opens the app, so use a relay-only account | "" |
Python cells. Notebooks can hold Python cells, run by Pyodide in a Web Worker that is only started (and only downloads Pyodide, ~10 MB plus packages) when the first Python cell runs. sql("SELECT ...") returns a pandas DataFrame from the notebook's active connection (Arrow IPC via pyarrow; columnar JSON as a fallback); await sql_async(...) works where the page is not cross-origin isolated. Imported packages that ship with Pyodide (numpy, pandas, matplotlib, ...) load on demand; open matplotlib figures render as PNGs. Interrupt terminates the worker, so variables from earlier cells are lost; runs also stop after 5 minutes. For air-gapped deployments, download the matching pyodide-0.29.5.tar.bz2 release, serve its pyodide/ folder (e.g. at /pyodide/) and set DUCK_UI_PYODIDE_BASE_URL. CSP: the shipped policy already allows the default source (script-src https://cdn.jsdelivr.net, 'wasm-unsafe-eval', connect-src https:); behind your own proxy, keep those (or your mirror's origin) in script-src/connect-src. 'unsafe-eval' is not granted, so a third-party package whose native code relies on eval will fail to load under the policy.
Build-time: DUCK_UI_BASEPATH=/subpath/ for subpath deploys, DUCK_UI_DUCKDB_WASM_CDN_ONLY=true for CDN-only artifacts.
Deployment guides: reverse proxy / nginx · hosting your data with CORS
git clone https://github.com/caioricciuti/duck-ui.git
cd duck-ui
bun install
bun run dev # http://localhost:5173
Checks, all of which CI runs:
bun run lint && bun run format:check && bun run typecheck && bun run test && bun run build
bun run test:e2e # Playwright, against the production build
Architecture notes live in docs/architecture and CONTRIBUTING.md.
Issues and PRs welcome. Read CONTRIBUTING.md first (it's short and it will save you time). Bug reports with a reproducible query are gold.
MIT. See LICENSE.
DuckDB · duckdb-wasm · Svelte · CodeMirror · Tailwind CSS · uPlot · Lucide
Want to sponsor Duck-UI? Get in touch.
193 followers · starred Feb 2025
459 followers · starred Oct 2025
834 followers · starred Feb 2025
60 followers · starred Oct 2025
The fully open-source DuckDB workbench that runs in your browser. SQL editor, notebooks, charts, AI assistant. No install, no signup, no backend — your data never leaves the tab.
See the codeThe fully open-source DuckDB workbench that runs in your browser.
No install, no signup, no backend. Your data never leaves the tab.
Try it now → duckui.com · Docs · Contributing

Or run it yourself:
docker run -p 5522:5522 ghcr.io/caioricciuti/duck-ui:latest
Open http://localhost:5522. That's the whole setup.
⌘K finds any of them, plus tables, saved queries and recent queries.EXPLAIN ANALYZE viewer. ⌘Enter runs the statement under the cursor, ⌘Shift+Enter the whole tab. A failed query is underlined where DuckDB points.sql() returning pandas DataFrames) and markdown cells, each with its own result and chart.$name placeholders get an input bar, in SQL tabs and notebooks..duckdb files. Drop a file on the explorer, paste a URL, or read straight from S3, GCS, Azure, R2 or MinIO. Local folders through the File System Access API.?load=ducklake: links, or plain ATTACH SQL.The whole app is built on Svelte 5 and CodeMirror 6. The startup bundle is about 350 kB of JavaScript; everything else, including the AI runtime, loads the first time you use it.
duckdb -ui?Both are good tools. The differences that matter:
| Duck-UI | Official DuckDB UI | |
|---|---|---|
| Frontend source | MIT, all of it in this repo | Closed source |
| Runs from | Any static host, Docker, or a URL | Local duckdb process, UI assets loaded remotely |
| Works offline / air-gapped | Yes | No |
| Self-host / embed / kiosk | Yes | No |
| AI assistant | Bring your own provider, or fully local WebLLM | MotherDuck account |
| External DuckDB servers | Yes (httpserver) | Local + MotherDuck only |
If you just want a quick local UI and don't care about any of that, the official one is fine. Duck-UI is for when the answers to "can I host it, embed it, extend it, run it offline, and read the code" need to be yes.
Any hosted dataset can become a one-click, runnable analysis. Add ?load= (and optionally &sql=) to the app URL. Try this one:
https://duckui.com/?load=https://blobs.duckdb.org/stations.parquet
&sql=SELECT country, count(*) AS stations FROM stations GROUP BY country ORDER BY stations DESC
Openers see exactly what will load and what will run, confirm once, and get a live editor over your data. Nothing touches a server. Parquet, CSV, JSON, .duckdb files, and ducklake: catalogs are supported. The Share dialog generates the link and a README badge for you:
The data host needs CORS enabled, see hosting your data for a free R2/GitHub Pages setup. More examples in the gallery.
The cli/ launcher serves the built app on 127.0.0.1 and opens it with your local files already queued to load, using the same ?load= links as above:
bun run build # once; the CLI serves dist/
node cli/duck-ui.js sales.csv events.parquet # or: npx duck-ui ... once published
node cli/duck-ui.js db.duckdb --port 6000 --no-open --sql "SHOW ALL TABLES"
CSV/TSV, Parquet, JSON/NDJSON and .duckdb files are supported. Only dist/ and the exact files you name are served (no directory listing or traversal), and requests must come from the local machine. The package is "private": true, so npx duck-ui works only after that flag is removed and the package (with a built dist/) is published.
Duck-UI doubles as a zero-backend data-publishing appliance. Drop a manifest next to the build, deploy to GitHub Pages, and visitors get a read-only SQL explorer for your data:
{
"ui": { "kiosk": true },
"databases": [
{ "name": "My Dataset", "file": "ducklake:https://pub-xxxx.r2.dev/catalog.ducklake" }
]
}
Bundled .db files, remote files over HTTPS, S3, and DuckLake catalogs are all supported. Remote sources are attached in place and read-only by default. See public/databases/README.md for the full manifest format and deploy steps.
Runtime environment variables (Docker):
| Variable | Description | Default |
|---|---|---|
DUCK_UI_EXTERNAL_CONNECTION_NAME | Name for a pre-configured external connection | "" |
DUCK_UI_EXTERNAL_HOST | Host URL for external DuckDB (may include a path) | "" |
DUCK_UI_EXTERNAL_PORT | Port for external DuckDB (NAME, HOST and PORT must all be set or the connection is skipped) | null |
DUCK_UI_EXTERNAL_API_KEY | API key sent as X-API-Key (takes priority over user/pass) | "" |
DUCK_UI_EXTERNAL_USER / DUCK_UI_EXTERNAL_PASS | Basic auth credentials | "" |
DUCK_UI_EXTERNAL_DATABASE_NAME | Database name for the external connection | "" |
DUCK_UI_ALLOW_UNSIGNED_EXTENSIONS | Allow unsigned DuckDB extensions | false |
DUCK_UI_DUCKDB_WASM_USE_CDN | Load DuckDB WASM from CDN | false |
DUCK_UI_DUCKDB_WASM_BASE_URL | Custom CDN base URL (the origin is added to the CSP automatically at container start) | auto jsDelivr |
DUCK_UI_PYODIDE_BASE_URL | Where Python notebook cells load Pyodide from: a URL or same-origin path (e.g. /pyodide/) of a Pyodide 0.29.5 "full" distribution folder. A cross-origin URL's origin is added to the CSP at container start | https://cdn.jsdelivr.net/pyodide/v0.29.5/full/ |
DUCK_UI_STUN_URLS | Comma-separated STUN servers for live sessions. Set it empty for a LAN-only deployment with no STUN at all | Google's public STUN |
DUCK_UI_TURN_URLS | Comma-separated TURN relays for live sessions between networks that cannot connect directly | none |
DUCK_UI_TURN_USERNAME / DUCK_UI_TURN_CREDENTIAL | TURN credentials. They reach every browser that opens the app, so use a relay-only account | "" |
Python cells. Notebooks can hold Python cells, run by Pyodide in a Web Worker that is only started (and only downloads Pyodide, ~10 MB plus packages) when the first Python cell runs. sql("SELECT ...") returns a pandas DataFrame from the notebook's active connection (Arrow IPC via pyarrow; columnar JSON as a fallback); await sql_async(...) works where the page is not cross-origin isolated. Imported packages that ship with Pyodide (numpy, pandas, matplotlib, ...) load on demand; open matplotlib figures render as PNGs. Interrupt terminates the worker, so variables from earlier cells are lost; runs also stop after 5 minutes. For air-gapped deployments, download the matching pyodide-0.29.5.tar.bz2 release, serve its pyodide/ folder (e.g. at /pyodide/) and set DUCK_UI_PYODIDE_BASE_URL. CSP: the shipped policy already allows the default source (script-src https://cdn.jsdelivr.net, 'wasm-unsafe-eval', connect-src https:); behind your own proxy, keep those (or your mirror's origin) in script-src/connect-src. 'unsafe-eval' is not granted, so a third-party package whose native code relies on eval will fail to load under the policy.
Build-time: DUCK_UI_BASEPATH=/subpath/ for subpath deploys, DUCK_UI_DUCKDB_WASM_CDN_ONLY=true for CDN-only artifacts.
Deployment guides: reverse proxy / nginx · hosting your data with CORS
git clone https://github.com/caioricciuti/duck-ui.git
cd duck-ui
bun install
bun run dev # http://localhost:5173
Checks, all of which CI runs:
bun run lint && bun run format:check && bun run typecheck && bun run test && bun run build
bun run test:e2e # Playwright, against the production build
Architecture notes live in docs/architecture and CONTRIBUTING.md.
Issues and PRs welcome. Read CONTRIBUTING.md first (it's short and it will save you time). Bug reports with a reproducible query are gold.
MIT. See LICENSE.
DuckDB · duckdb-wasm · Svelte · CodeMirror · Tailwind CSS · uPlot · Lucide
Want to sponsor Duck-UI? Get in touch.
193 followers · starred Feb 2025
459 followers · starred Oct 2025
834 followers · starred Feb 2025
60 followers · starred Oct 2025