@duckviz/explorer
Three-panel data explorer with file tree, data grid, SQL workspace, and AI widget recommendations.
@duckviz/explorer provides the <Explorer /> component — the same three-panel layout used in the DuckViz app. It handles file ingestion, data viewing, SQL queries, and AI-powered chart recommendations.
Installation
npm install @duckviz/explorer @duckviz/db @duckviz/dashboard @duckviz/ui @duckviz/widgets
npm install react react-dom next zustand @tanstack/react-query @duckdb/duckdb-wasm apache-arrow d3Quick start
import { DuckvizDBProvider } from "@duckviz/db";
import { Explorer, type ExplorerDataset } from "@duckviz/explorer";
function App() {
const datasets: ExplorerDataset[] = [
{ id: "users", name: "Users", tableName: "t_users" },
];
return (
<DuckvizDBProvider persistence>
<Explorer
datasets={datasets}
dashboards={[]}
onAddWidgetToDashboard={() => ({ ok: true })}
onCreateDashboard={(name) => crypto.randomUUID()}
authenticated={true}
/>
</DuckvizDBProvider>
);
}<Explorer> props
Prop
Type
Two modes: File mode vs. Dataset mode
File mode (default)
When datasets is not provided, Explorer shows its full file-upload UI:
- Drag-and-drop file upload
- Folder upload with recursive directory walking
- File tree sidebar with unmount controls
- Full ingestion pipeline (Web Worker parsing, batched insert)
Dataset mode
When datasets is provided, Explorer skips the file-upload flow and renders a flat sidebar listing the given tables. The prop accepts two shapes:
Pre-ingested tables (ExplorerDataset[]) — when you've already called db.ingest() yourself:
<Explorer
datasets={[{ id: "orders", name: "Orders", tableName: "t_orders" }]}
// ... other props
/>Auto-ingest (DuckvizDataset[]) — when you want Explorer to ingest row data for you:
import type { DuckvizDataset } from "@duckviz/db";
const datasets: DuckvizDataset[] = [
{ name: "Orders", data: ordersRows }, // → t_orders
{ name: "Users", data: userRows, tableName: "t_users" },
];
<Explorer datasets={datasets} /* ... */ />Tables auto-ingested this way are dropped from DuckDB on unmount by default. See the Datasets Mode Guide for a full walkthrough.
customFetch — API path rewriting
Explorer makes API calls to DuckViz endpoints (for AI widgets, log detection, etc.). By default, it calls /api/widget-flow/*, /api/detect-log-format, etc.
If you're self-hosting or proxying through your own backend, use customFetch to rewrite paths:
const customFetch: FetchFn = (input, init) => {
// Rewrite /api/* to /api/duckviz/*
if (typeof input === "string" && input.startsWith("/api/")) {
input = input.replace(/^\/api\//, "/api/duckviz/");
}
return fetch(input, init);
};
<Explorer customFetch={customFetch} /* ... */ />See Server Proxy Setup for the complete server-side configuration.
Exported types
// A pre-ingested table to display in the Explorer
interface ExplorerDataset {
id: string; // Unique identifier
name: string; // Display name in the sidebar
tableName: string; // DuckDB table name (e.g. "t_users")
}
// Dashboard reference for the "Add to Dashboard" dropdown
interface ExplorerDashboardRef {
id: string;
name: string;
}
// Result of adding a widget to a dashboard
interface AddWidgetResult {
ok: boolean;
error?: string;
}
// Custom fetch function type
type FetchFn = (
input: RequestInfo | URL,
init?: RequestInit
) => Promise<Response>;
// Widget data generated by the AI
interface RecommendedWidget {
type: string; // Chart type (e.g. "bar", "scatter", "heatmap")
title: string; // Human-readable title
description: string; // What the chart shows
duckdbQuery: string; // SQL query to execute
config?: Record<string, unknown>;
domain?: string; // Detected data domain
}Internal subpath
@duckviz/explorer/internal exports low-level store access (resetExplorerStores, clearWidgetSessionForTable) for host apps that need deeper control. External consumers typically don't need this.
Example: Explorer with dashboard integration
import { Explorer, type RecommendedWidget } from "@duckviz/explorer";
import { useDashboardStore } from "./stores/dashboard";
function MyExplorer() {
const dashboards = useDashboardStore((s) => s.dashboards);
const addWidget = useDashboardStore((s) => s.addWidget);
const createDashboard = useDashboardStore((s) => s.createDashboard);
return (
<Explorer
dashboards={dashboards.map((d) => ({ id: d.id, name: d.name }))}
onAddWidgetToDashboard={(dashboardId, widget) => {
addWidget(dashboardId, widget);
return { ok: true };
}}
onCreateDashboard={(name) => createDashboard(name)}
authenticated={true}
persistence
/>
);
}