DuckDB Terminal - v0.2.0
    Preparing search index...

    DuckDB Terminal - v0.2.0

    DuckDB Terminal

    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.

    • Full SQL REPL - Execute SQL queries with multi-line support
    • Command History - Navigate previous commands with arrow keys (persisted in IndexedDB)
    • Auto-Complete - Tab completion for SQL keywords, table names, and functions
    • Multiple Output Modes - Table, CSV, TSV, or JSON output formats
    • Clipboard Support - Copy query results to clipboard in any output format
    • Result Pagination - Navigate large result sets page by page
    • Syntax Highlighting - Color-coded SQL keywords, strings, and numbers
    • Clickable URLs - Automatically detect and make URLs clickable in results
    • File Loading - Load CSV, Parquet, and JSON files via drag-and-drop or file picker
    • Dark/Light Themes - Switchable themes with custom theme support
    • Customizable Prompts - Configure primary and continuation prompts
    • Dot Commands - Terminal commands like .help, .tables, .schema
    • Query Timing - Optional execution time display
    • Persistent Storage - Optional OPFS storage for data persistence
    ┌─────────────────────────────────────────────────────────────────────────────┐
    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:

    1. Use .mode csv or .mode tsv then .copy to paste into a spreadsheet
    2. Use .mode json then .copy to paste into a JSON file or API tool
    3. Use .mode table then .copy to paste formatted output into documentation

    For 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);
    • Modern browser with WebAssembly support
    • SharedArrayBuffer (requires COOP/COEP headers)

    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