Transform static SQL code blocks into interactive snippets powered by DuckDB WASM.
🎮 Try it live - Interactive examples and demos
Features
- 🦆 DuckDB in the Browser - Full SQL analytics engine running locally
- ✨ Zero Backend - Everything runs client-side in WebAssembly
- 🎨 Syntax Highlighting - Beautiful SQL code formatting with cursor preservation
- ⚡ Instant Results - Execute queries with one click or Ctrl/Cmd+Enter
- 🔧 Easy Integration - Works with any static site or documentation
- 🧩 Configurable - Tweak selectors, auto-init, editable mode, and UI affordances
- 🗂️ Init Queries - Install DuckDB extensions or run setup SQL once per page
- ♻️ Reset Queries - Optionally run cleanup SQL when the editor resets
- 🎨 Custom Themes - Extend light/dark defaults or register fully custom palettes
- ♿ Accessible - Full ARIA support and keyboard navigation
- 🎯 Lightweight - Only ~22KB minified, loads DuckDB on-demand
- 🌙 Dark Mode - Automatic theme detection or manual control
- 🔒 Secure - No data leaves the browser, CSP-compatible
- 📁 Relative Paths - Automatic resolution of relative parquet file paths
Installation
CDN (Recommended)
The easiest way to get started is via CDN:
<!-- Latest version --> <script src="https://unpkg.com/pondpilot-widget"></script> <!-- Specific version (recommended for production) --> <script src="https://unpkg.com/pondpilot-widget@1.4.0"></script> <!-- Alternative CDN --> <script src="https://cdn.jsdelivr.net/npm/pondpilot-widget"></script>
Package Manager
For projects using a bundler:
NPM
npm install pondpilot-widget
Yarn
yarn add pondpilot-widget
Quick Start
1. Add the Script Tag
<script src="https://unpkg.com/pondpilot-widget"></script>
2. Mark Your SQL Code Blocks
<pre class="pondpilot-snippet"> SELECT 'Hello World' as greeting, 42 as answer, CURRENT_DATE as today; </pre>
3. That's It!
The widget automatically transforms your static code blocks into interactive SQL editors.
Alternative: Using a Bundler
If you're using a bundler like Webpack, Vite, or Parcel:
import "pondpilot-widget";
Usage Examples
Basic Configuration
// Configure before initialization window.PondPilot.config.theme = 'dark'; window.PondPilot.config.showPoweredBy = false; // Or initialize with options new PondPilot.Widget(element, { theme: 'dark', editable: false, showPoweredBy: false });
Framework Integration
React
import { useEffect, useRef } from "react"; import "pondpilot-widget"; function SQLEditor({ sql, ...options }) { const ref = useRef(null); useEffect(() => { if (ref.current) { const widget = new window.PondPilot.Widget(ref.current, options); return () => widget.destroy(); } }, []); return ( <pre ref={ref} className="pondpilot-snippet"> {sql} </pre> ); }
Advanced Configuration
The runtime exposes a global API with ergonomic helpers:
const { config, init, create, destroy, registerTheme, getConfig } = window.PondPilot; // Merge default configuration config({ selector: "pre.sql-demo", baseUrl: "https://cdn.example.com/datasets", autoInit: false, initQueries: ["INSTALL httpfs", "LOAD httpfs"], resetQueries: ["DROP TABLE IF EXISTS scratch_table;"], }); // Register a custom theme and apply it via data-theme or options.theme registerTheme("sunset", { extends: "light", config: { bgColor: "#ffedd5", textColor: "#7c2d12", borderColor: "#f97316", editorBg: "#fff7ed", editorText: "#7c2d12", editorFocusBg: "#fed7aa", controlsBg: "rgba(249, 115, 22, 0.12)", primaryBg: "#f97316", primaryText: "#ffffff", primaryHover: "#ea580c", secondaryBg: "rgba(249, 115, 22, 0.16)", secondaryText: "#7c2d12", secondaryHover: "rgba(249, 115, 22, 0.28)", mutedText: "#9a3412", errorText: "#dc2626", errorBg: "rgba(220, 38, 38, 0.08)", errorBorder: "rgba(220, 38, 38, 0.2)", tableHeaderBg: "rgba(249, 115, 22, 0.16)", tableHeaderText: "#7c2d12", tableHover: "rgba(249, 115, 22, 0.12)", syntaxKeyword: "#c2410c", syntaxString: "#047857", syntaxNumber: "#7c3aed", syntaxComment: "#9a3412", syntaxSpecial: "#dc2626", syntaxIdentifier: "#facc15", fontFamily: "Inter, system-ui, sans-serif", editorFontFamily: "'JetBrains Mono', monospace", fontSize: "14px", editorFontSize: "13px", buttonFontSize: "13px", metadataFontSize: "12px", }, }); // Manually mount widgets when needed document.querySelectorAll("pre.sql-demo").forEach((node) => { window.PondPilot.create(node, { theme: "sunset" }); });
Data attributes
Per-snippet overrides can be expressed declaratively:
<pre class="pondpilot-snippet" data-theme="sunset" data-base-url="https://cdn.example.com/data" data-init-queries='["LOAD httpfs"]' > <code>SELECT * FROM 'sales.parquet';</code> </pre>
Supported attributes include:
data-themedata-base-urldata-editabledata-show-powered-bydata-init-queries(semicolon or JSON array)
API Summary
PondPilot.init(target?, options?)– initialize matching elements (defaults toconfig.selector)PondPilot.create(element, options?)– mount a single instance on demandPondPilot.destroy(target?)– remove widgets (defaults to all)PondPilot.config(partial)– merge configurationPondPilot.getConfig()– inspect current configurationPondPilot.registerTheme(name, definition)– extend theming systemPondPilot.Widget– constructor for manual control (new PondPilot.Widget(element, options))
Vue
<template> <pre ref="editor" class="pondpilot-snippet">{{ sql }}</pre> </template> <script> import 'pondpilot-widget'; export default { props: ['sql', 'options'], mounted() { this.widget = new window.PondPilot.Widget(this.$refs.editor, this.options); }, beforeUnmount() { this.widget?.destroy(); } } </script>
Markdown/Documentation Sites
Docusaurus
// In docusaurus.config.js module.exports = { scripts: [ 'https://unpkg.com/pondpilot-widget' ], // ... };
VitePress
// In .vitepress/theme/index.js import DefaultTheme from 'vitepress/theme'; import 'pondpilot-widget'; export default DefaultTheme;
Local Examples (in this repo)
You can run a small examples site from the examples/ folder:
- Serve the repository root (so examples/ is web‑served):
python3 -m http.server 8080
# then open http://localhost:8080/examples/index.html- Local .duckdb database demo (optional):
# create a tiny demo DB at examples/data/blog.duckdb uv run examples/create-blog-db.py # or: pip install duckdb pandas && python3 examples/create-blog-db.py # open the demo page open http://localhost:8080/examples/local-duckdb.html
- Relative Parquet path handling demo (optional):
cd examples/relative-paths python3 create-test-data.py # creates analytics.parquet in this directory cd ../.. open http://localhost:8080/examples/relative-paths/test-relative-paths.html
- A5 Geospatial Extension demo with interactive map:
# No setup required - just open the demo
open http://localhost:8080/examples/a5-geospatial-demo.htmlNotes:
- The example pages in this repo load the widget from ../src/ for local development. You can switch them to use the CDN by replacing the script tag with the CDN snippet from Installation.
- The local .duckdb example shows how to provide an external DuckDB WASM instance to the widget and open a DB file served over HTTP.
- The relative‑paths example demonstrates how the widget auto‑resolves relative parquet file paths in queries.
- The A5 demo shows how to use DuckDB community extensions for geospatial analysis with interactive Leaflet map visualization.
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
selector |
string | "pre.pondpilot-snippet, .pondpilot-snippet pre" |
CSS selector for SQL blocks |
theme |
string | "light" |
Theme: "light" or "dark" |
editable |
boolean | true |
Allow editing SQL code |
showPoweredBy |
boolean | true |
Show "Powered by PondPilot" branding |
baseUrl |
string | "http://localhost:5173" |
PondPilot instance URL |
autoInit |
boolean | true |
Auto-initialize on DOM ready |
API Reference
Global Object
window.PondPilot = { init(), // Initialize all widgets destroy(), // Clean up all widgets config: {}, // Global configuration Widget: class {} // Widget constructor };
Widget Instance
const widget = new PondPilot.Widget(element, options); // Methods await widget.run(); // Execute SQL query widget.reset(); // Reset to original code await widget.cleanup(); // Clean up resources widget.destroy(); // Destroy widget
Security Considerations
-
Content Security Policy (CSP)
Content-Security-Policy: script-src 'self' https://unpkg.com https://cdn.jsdelivr.net; worker-src 'self' blob: https://cdn.jsdelivr.net; connect-src 'self' https://cdn.jsdelivr.net; -
Subresource Integrity (SRI)
<script src="https://unpkg.com/pondpilot-widget@1.4.0" integrity="sha384-[hash]" crossorigin="anonymous"> </script>
Browser Support
- Chrome/Edge 88+
- Firefox 89+
- Safari 15+
Requires:
- WebAssembly
- Web Workers
- ES2018+
Working with Parquet Files
The widget supports loading parquet files from various sources:
Direct HTTP URLs
SELECT * FROM 'https://example.com/data.parquet' LIMIT 10;
Relative Paths (New in v1.1.0)
The widget automatically resolves relative paths to absolute URLs:
-- All of these formats are supported: SELECT * FROM 'data.parquet'; SELECT * FROM './data.parquet'; SELECT * FROM '/data.parquet';
When using relative paths:
- Files must be accessible via HTTP from the same server
- The widget resolves paths relative to the current page URL
- For local development, ensure your files are served by a web server
Performance Tips
- Lazy Loading: DuckDB is only loaded when first query runs
- Shared Instance: Multiple widgets share the same DuckDB instance
- Resource Cleanup: Widgets automatically clean up when removed from DOM
- File Caching: Registered parquet files are cached to avoid duplicate downloads
Changelog
See CHANGELOG.md for release history.
License
MIT © PondPilot
Contributing
Contributions welcome! Please read our contributing guide.
Local development
npm install # install dependencies npm run format # apply Prettier formatting npm test # run the Vitest suite
Prefer just? We provide a lightweight justfile with the same commands (just format, just test, etc.).