A browser-based SQL Terminal for DuckDB powered by Ghostty terminal emulator.
The latest version is always deployed to https://terminal.sql-workbench.com.
The TypeScript API Docs can be found at https://tobilg.github.io/duckdb-terminal.
.help, .tables, .schema┌─────────────────────────────────────────────────────────────────────────────┐
│ Browser │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ DuckDB Terminal │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Terminal │ │ │
│ │ │ │ │ │
│ │ │ - REPL (input, output, history) │ │ │
│ │ │ - Command parsing (SQL, dot commands, multi-line) │ │ │
│ │ │ - Result formatting (table, CSV, TSV, JSON) │ │ │
│ │ │ - Syntax highlighting │ │ │
│ │ └───────┬─────────────────────┬─────────────────────────┬─────────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ │ │ │
│ │ ┌───────────────┐ ┌─────────────────────┐ │ │ │
│ │ │ Terminal │ │ Database │ │ │ │
│ │ │ Adapter │ │ │ │ │ │
│ │ │ │ │ - DuckDB WASM │ │ │ │
│ │ │ - Ghostty │ │ wrapper │ │ │ │
│ │ │ - Themes │ │ - Query execution │ │ │ │
│ │ │ - Keyboard │ │ - Auto-complete │ │ │ │
│ │ │ - Mobile │ │ - File loading │ │ │ │
│ │ └───────┬───────┘ └─────────┬───────────┘ │ │ │
│ │ │ │ │ │ │
│ └──────────┼─────────────────────┼─────────────────────────┼────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Ghostty Web │ │ DuckDB WASM │ │ IndexedDB │ │
│ │ (npm package) │ │ (Web Worker) │ │ (Command History) │ │
│ │ │ │ │ │ │ │
│ │ - Canvas rendering │ │ - SQL engine │ └─────────────────────┘ │
│ │ - VT100 emulation │ │ - Query processing │ │
│ └────────────────────┘ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ OPFS │ │
│ │ (Database Storage) │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
npm install duckdb-terminal
import { createTerminal } from 'duckdb-terminal';
const Terminal = await createTerminal({
container: '#terminal',
theme: 'dark',
});
git clone https://github.com/tobilg/duckdb-terminal.git
cd duckdb-terminal
npm install
npm run dev
Open http://localhost:5173 in your browser.
interface TerminalConfig {
// Container element or CSS selector
container: HTMLElement | string;
// Font family (default: 'Fira Code', 'Cascadia Code', etc.)
fontFamily?: string;
// Font size in pixels (default: 14)
fontSize?: number;
// Theme: 'dark' | 'light' | Theme (default: 'dark')
theme?: 'dark' | 'light' | Theme;
// Storage: 'memory' | 'opfs' (default: 'memory')
storage?: 'memory' | 'opfs';
// Database path for OPFS storage
databasePath?: string;
// Show welcome message (default: true)
// When enabled, displays loading progress during DuckDB initialization
welcomeMessage?: boolean;
// Primary prompt string (default: '🦆 ')
prompt?: string;
// Continuation prompt for multi-line SQL (default: ' > ')
continuationPrompt?: string;
// Enable clickable URL detection (default: true)
linkDetection?: boolean;
// Scrollback buffer size in bytes (default: 10485760 = 10MB)
scrollback?: number;
}
| Command | Description |
|---|---|
.help |
Show available commands |
.clear |
Clear the terminal |
.tables |
List all tables |
.schema <table> |
Show table schema |
.timer on|off |
Toggle query timing |
.mode table|csv|tsv|json |
Set output format |
.copy |
Copy last query results to clipboard |
.pagesize <n> |
Set pagination size (0 to disable) |
.theme dark|light |
Switch color theme (clears screen) |
.highlight on|off |
Toggle syntax highlighting |
.links on|off |
Toggle clickable URL detection |
.files [list|add|remove] |
Manage loaded files (list, add, or remove) |
.open |
Open file picker to load data files |
.prompt [primary [cont]] |
Get or set the command prompt |
.examples |
Show example queries |
.reset |
Reset database and all settings to defaults |
| Key | Action |
|---|---|
Enter |
Execute command/SQL |
Tab |
Auto-complete |
Backspace |
Delete character before cursor |
Delete |
Delete character at cursor |
↑ / ↓ |
Navigate history |
← / → |
Move cursor |
Home |
Move to start of line |
End |
Move to end of line |
Ctrl+A |
Move to start of line |
Ctrl+E |
Move to end of line |
Ctrl+K |
Clear from cursor to end of line |
Ctrl+U |
Clear entire line |
Ctrl+V |
Paste from clipboard |
Ctrl+C |
Cancel current input |
When viewing paginated results, the following keys are available:
| Key | Action |
|---|---|
n / ↓ / Enter |
Next page |
p / ↑ |
Previous page |
q / Escape / Ctrl+C |
Quit pagination |
-- Create a table
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name VARCHAR,
email VARCHAR
);
-- Insert data
INSERT INTO users VALUES
(1, 'Alice', 'alice@example.com'),
(2, 'Bob', 'bob@example.com');
-- Query data
SELECT * FROM users WHERE name LIKE 'A%';
-- Use built-in functions
SELECT range(10), current_timestamp;
The terminal supports four output formats and provides an easy way to copy query results to your clipboard.
Use the .mode command to switch between output formats:
-- Table format (default) - human-readable ASCII table
.mode table
SELECT * FROM users;
+----+-------+-------------------+
| id | name | email |
+----+-------+-------------------+
| 1 | Alice | alice@example.com |
| 2 | Bob | bob@example.com |
+----+-------+-------------------+
-- CSV format - comma-separated values
.mode csv
SELECT * FROM users;
id,name,email
1,Alice,alice@example.com
2,Bob,bob@example.com
-- TSV format - tab-separated values
.mode tsv
SELECT * FROM users;
id name email
1 Alice alice@example.com
2 Bob bob@example.com
-- JSON format - array of objects
.mode json
SELECT * FROM users;
[
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"}
]
After running a query, use .copy to copy the results to your clipboard in the current output format:
-- Run your query
SELECT * FROM users WHERE active = true;
-- Copy results to clipboard (uses current .mode format)
.copy
The copied content respects your current output mode, so you can:
.mode csv or .mode tsv then .copy to paste into a spreadsheet.mode json then .copy to paste into a JSON file or API tool.mode table then .copy to paste formatted output into documentationFor large result sets, enable pagination with .pagesize:
-- Enable pagination (50 rows per page)
.pagesize 50
-- Run a query with many results
SELECT * FROM large_table;
-- Navigate pages:
-- n or Enter - next page
-- p - previous page
-- 1-9 - jump to page number
-- q - quit pagination
Set .pagesize 0 to disable pagination and show all results at once.
Note: Queries that already contain LIMIT or OFFSET clauses bypass pagination, giving you full control over result size.
You can create custom themes by providing a Theme object instead of 'dark' or 'light':
import { createTerminal, type Theme, type ThemeColors } from 'duckdb-terminal';
// Define custom colors
const myColors: ThemeColors = {
background: '#1a1b26', // Terminal background
foreground: '#a9b1d6', // Default text color
cursor: '#c0caf5', // Cursor color
selection: '#33467c', // Selection highlight
// Standard ANSI colors
black: '#15161e',
red: '#f7768e',
green: '#9ece6a',
yellow: '#e0af68',
blue: '#7aa2f7',
magenta: '#bb9af7',
cyan: '#7dcfff',
white: '#a9b1d6',
// Bright variants
brightBlack: '#414868',
brightRed: '#f7768e',
brightGreen: '#9ece6a',
brightYellow: '#e0af68',
brightBlue: '#7aa2f7',
brightMagenta: '#bb9af7',
brightCyan: '#7dcfff',
brightWhite: '#c0caf5',
};
// Create theme object
const tokyoNight: Theme = {
name: 'tokyo-night',
colors: myColors,
};
// Use custom theme
const Terminal = await createTerminal({
container: '#terminal',
theme: tokyoNight,
});
// You can also change theme at runtime
Terminal.setTheme(tokyoNight);
| Property | Description | ANSI Code |
|---|---|---|
background |
Terminal background color | - |
foreground |
Default text color | - |
cursor |
Cursor color | - |
selection |
Text selection highlight | - |
black |
ANSI black | \x1b[30m |
red |
ANSI red | \x1b[31m |
green |
ANSI green | \x1b[32m |
yellow |
ANSI yellow | \x1b[33m |
blue |
ANSI blue | \x1b[34m |
magenta |
ANSI magenta | \x1b[35m |
cyan |
ANSI cyan | \x1b[36m |
white |
ANSI white | \x1b[37m |
brightBlack |
Bright black (gray) | \x1b[90m |
brightRed |
Bright red | \x1b[91m |
brightGreen |
Bright green | \x1b[92m |
brightYellow |
Bright yellow | \x1b[93m |
brightBlue |
Bright blue | \x1b[94m |
brightMagenta |
Bright magenta | \x1b[95m |
brightCyan |
Bright cyan | \x1b[96m |
brightWhite |
Bright white | \x1b[97m |
The library exports two built-in themes that you can use directly or extend:
import { darkTheme, lightTheme } from 'duckdb-terminal';
// Use directly
const Terminal = await createTerminal({
container: '#terminal',
theme: darkTheme,
});
// Or extend
const myTheme: Theme = {
name: 'my-dark',
colors: {
...darkTheme.colors,
background: '#000000', // Override specific colors
cursor: '#ff0000',
},
};
Creates and starts a DuckDB Terminal instance.
import { createTerminal } from 'duckdb-terminal';
const Terminal = await createTerminal({
container: document.getElementById('terminal'),
theme: 'dark',
});
// Write to terminal
Terminal.write('Hello, World!');
Terminal.writeln('With newline');
// Execute SQL programmatically
const result = await Terminal.executeSQL('SELECT 1+1 as answer');
console.log(result); // { columns: ['answer'], rows: [[2]], rowCount: 1, duration: 5 }
// Change theme
Terminal.setTheme('light');
// Clear terminal
Terminal.clear();
// Clean up when done (removes event listeners, closes database)
Terminal.destroy();
The Terminal emits events that you can subscribe to for monitoring and integrating with your application:
import { createTerminal, type TerminalEvents } from 'duckdb-terminal';
const Terminal = await createTerminal({
container: '#terminal',
theme: 'dark',
});
// Subscribe to events
Terminal.on('ready', () => {
console.log('Terminal is ready!');
});
Terminal.on('queryStart', ({ sql }) => {
console.log('Executing:', sql);
});
Terminal.on('queryEnd', ({ sql, result, error, duration }) => {
if (error) {
console.error('Query failed:', error);
} else {
console.log(`Query completed in ${duration}ms, ${result?.rowCount} rows`);
}
});
Terminal.on('stateChange', ({ state, previous }) => {
console.log(`State changed: ${previous} -> ${state}`);
});
Terminal.on('themeChange', ({ theme, previous }) => {
console.log(`Theme changed to: ${theme.name}`);
});
Terminal.on('fileLoaded', ({ filename, size, type }) => {
console.log(`Loaded file: ${filename} (${size} bytes)`);
});
Terminal.on('commandExecute', ({ command, args }) => {
console.log(`Command: ${command}`, args);
});
Terminal.on('error', ({ message, source }) => {
console.error(`Error from ${source}: ${message}`);
});
// Unsubscribe using the returned function
const unsubscribe = Terminal.on('queryEnd', handler);
unsubscribe(); // Stop listening
// Or use off() directly
Terminal.off('queryEnd', handler);
| Event | Payload | Description |
|---|---|---|
ready |
{} |
Terminal is fully initialized |
queryStart |
{ sql } |
SQL query execution started |
queryEnd |
{ sql, result, error?, duration } |
SQL query completed or failed |
stateChange |
{ state, previous } |
Terminal state changed (idle, collecting, executing, paginating) |
themeChange |
{ theme, previous } |
Theme was changed |
fileLoaded |
{ filename, size, type } |
File was loaded via drag-drop or picker |
commandExecute |
{ command, args } |
Dot command was executed |
error |
{ message, source } |
An error occurred |
import {
Terminal,
Database,
TerminalAdapter,
formatTable,
formatCSV,
formatJSON
} from 'duckdb-terminal';
// Use Database directly
const db = new Database({ storage: 'memory' });
await db.init();
const result = await db.executeQuery('SELECT 42');
// Use formatters
console.log(formatTable(result.columns, result.rows));
console.log(formatCSV(result.columns, result.rows));
console.log(formatJSON(result.columns, result.rows));
// Get completions
const suggestions = await db.getCompletions('SEL', 3);
For development, Vite automatically sets the required headers. For production, configure your server:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
# Development
npm run dev
# Production build
npm run build
# Run tests
npm test
# Type checking
npm run typecheck
| File | Format | Usage |
|---|---|---|
dist/duckdb-terminal.js |
ESM | Modern bundlers |
dist/duckdb-terminal.umd.cjs |
UMD | Script tags, legacy |
dist/*.d.ts |
TypeScript | Type definitions |
MIT