DuckVizBeta
Packages

@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 d3

Quick 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
    />
  );
}