@duckviz/report
Report editor and deck presenter with PDF, DOCX, and PPTX export.
@duckviz/report turns a dashboard config into a full written report (Notion-like block editor, PDF/DOCX export) or a presenter deck (slideshow + PPTX export). The same SSE pipeline that powers the hosted DuckViz product is bundled here — just pass your widgets and go.
Installation
npm install @duckviz/report @duckviz/db @duckviz/ui @duckviz/widgets
npm install react react-domThe package also has several optional peer dependencies (@yoopta/editor + plugins, docx, jspdf, pptxgenjs). Install them when the bundler complains — all are required for full functionality.
Quick start
import { DuckvizDBProvider } from "@duckviz/db";
import { ReportBuilder } from "@duckviz/report";
import type { BuilderDashboardConfig } from "@duckviz/report";
import type { DuckvizDataset } from "@duckviz/db";
const config: BuilderDashboardConfig = {
name: "Q4 Sales",
widgets: [
{
id: "revenue",
type: "bar",
title: "Revenue by Category",
description: "Top 10 categories",
dataKey: "SELECT category, SUM(amount) AS value FROM t_sales GROUP BY category ORDER BY value DESC LIMIT 10",
},
],
};
const datasets: DuckvizDataset[] = [
{ name: "Sales", data: salesRows, tableName: "t_sales" },
];
function App({ onBack }: { onBack: () => void }) {
return (
<DuckvizDBProvider persistence>
<ReportBuilder config={config} datasets={datasets} onBack={onBack} />
</DuckvizDBProvider>
);
}<ReportBuilder> props
Prop
Type
The builder handles:
- Instant scaffold — H1 title + H2 section headings + chart placeholders render immediately, no LLM wait.
- Streaming generation — parallel SSE calls per section (3 concurrent) fill in written content as each completes.
- In-place editing — Slate.js block editor (headings, lists, quotes, callouts, dividers, code, tables, images).
- Chat modifications — right-panel chat re-writes sections via
/api/modify-report-section. - Pre-export dialog — A4 preview, editable report + dashboard names, optional cover page, optional clickable table of contents (
1.2.0). - Export — programmatic PDF (jsPDF) and DOCX (docx.js) that walk
StructuredReportblock by block. Only chart SVGs are rasterized; text and tables stay vector/native. Data-table widgets export as real tables (first 30 rows,1.2.3); TOC bookmarks are clickable in both formats (1.2.2).
<DeckBuilder> props
Same shape as ReportBuilder, plus:
Prop
Type
import { DeckBuilder } from "@duckviz/report";
<DeckBuilder
config={config}
datasets={datasets}
onBack={handleBack}
onPresenting={(p) => setFullscreen(p)}
/>The deck builder generates a StructuredDeck (title + slides), renders a presenter UI, and exports to PowerPoint via pptxgenjs.
Pre-fetch async data — don't pass a Promise or loader
<ReportBuilder> and <DeckBuilder> accept a resolved array of DuckvizDataset. The async loader mode is Explorer-only — Report and Deck silently render empty if you pass a Promise, an async function, or a loader that returns datasets without an activeId. Await your fetch first, then pass the array.
Templates
import { REPORT_TEMPLATES, getRelevantTemplateIds } from "@duckviz/report";
// Find templates relevant to the data domains in your widgets
const domains = ["Sales & Revenue", "Customer Analytics"];
const relevant = getRelevantTemplateIds(domains);
// → ["executive-summary", "sales-analysis", ...]
const template = REPORT_TEMPLATES.find((t) => t.id === relevant[0]);Each template declares its sections (SectionTemplate[]) and a systemPromptHint that steers the LLM's tone.
Power-user composition
If the high-level builders don't fit (custom toolbars, resizable panels, Mantine chrome), drop to the lower-level components:
import {
ReportProvider,
ReportPageContent,
createReportCallbacks,
DeckView,
DeckRightPanel,
DeckPresenter,
DeckChartCapture,
useDeckGeneration,
exportToPptx,
} from "@duckviz/report";createReportCallbacks({ customFetch, runQuery }) returns the SSE callback bundle that ReportProvider expects. You own the layout; the package owns the state machine.
exportToPptx(deck, options)
Standalone PPTX export — call from a button click, not only from <DeckBuilder>:
import { exportToPptx } from "@duckviz/report";
await exportToPptx(structuredDeck, {
fileName: "q4-review.pptx",
branding: { companyName: "Acme", logoDataUrl: "..." },
});useDeckGeneration()
Low-level SSE hook for decks. Returns { phase, slides, start, modify, reset } — use when you render your own presenter chrome but still want the package's state machine driving the stream.
Server entry
API route code should import from the server subpath so React/DOM don't leak into server bundles:
import type {
StructuredReport,
ReportSection,
StructuredDeck,
Slide,
DeckVariableSpec,
ValidatedWidget,
} from "@duckviz/report/server";
import {
// Template engine (pure, deterministic, no React)
parse,
evaluate,
processTemplate,
processTemplateRich,
formatCurrency,
formatPercent,
formatDate,
formatDuration,
abbreviateNumber,
TEMPLATE_SYNTAX_REFERENCE,
// Prompt builders
REPORT_SYSTEM_PROMPT,
buildSectionPrompt,
DECK_SYSTEM_PROMPT,
buildChartSlidePrompt,
buildSummarySlidePrompt,
// Server-side spec validator (runs after LLM output)
validateSpecs,
} from "@duckviz/report/server";Template engine
Slides can declare variables ({{metric}}, {{delta | percent}}) that are evaluated server-side against the widget's SQL result. parse → evaluate → processTemplate is the full pipeline; the format* helpers drive number / currency / date rendering. TEMPLATE_SYNTAX_REFERENCE is a string constant you can paste into a system prompt so the LLM emits syntactically correct templates. See /docs/guides/deck-integration for the end-to-end flow.
Export types
The @duckviz/report/server entry is the canonical place for all non-React report/deck types — use it from any Next.js API route, Node proxy, or Cloudflare Worker.
Exported types
// Client (default entry)
import type {
ReportBuilderProps,
DeckBuilderProps,
BuilderDashboardConfig,
BuilderWidget,
CompanyBranding,
ReportSection,
StructuredReport,
StructuredDeck,
BuildPhase,
DeckBuildPhase,
WidgetInput,
FetchFn,
} from "@duckviz/report";
// Server (API routes only)
import type { StructuredReport, ReportSection } from "@duckviz/report/server";Styles
The package auto-injects its stylesheet via "sideEffects": ["*.css"]. If your bundler strips side-effects, import explicitly:
import "@duckviz/report/styles.css";