The analytics document editor for data teams.

Infigure is where insights are created, live, and shared. It works like a document editor, but charts, tables, and metrics run on live queries, so the page you publish stays current for everyone who reads it.

Editor
Document
Presentation
Publish Site
Embedded File

Build every figure your way.

Prompt an agent, start from a production component, write the code, or style it with your design system.

MCP prompt

Describe the figure you want. An agent over MCP creates the blocks, binds them to the data and writes the code.

Component library

Charts, tables and KPIs ready to drop in, styled by your design tokens.

NYC taxi trips

Blocks

Tables, metrics, shapes and documents, from one palette.

Inspector controls

Colour, spacing, type and publishing sit in the same inspector you use in the editor.

SQL engine

Tables, sheets, and metrics all run on a centralised DuckDB engine with a first-class SQL editor. Connect the major data sources, so the numbers in your document stay current.

Snowflake
CSV
Parquet
Python
S3

Code panel

Every visual is a React file you can open, read and change.

LineChart.tsx
1"use client";
2
3import { defineInfigure } from "@infigure/sdk";
4import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis } from "recharts";
5
6// A daily series as an area chart, under its label. The bound table arrives as its rows.
7
8type Row = Record<string, unknown>;
9
10const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
11
12const rowsOf = (bound: unknown): Row[] => {
13 if (Array.isArray(bound)) return bound as Row[];
14 if (!bound || typeof bound !== "object") return [];
15 const record = bound as Record<string, unknown>;
16 if (typeof record.toArray === "function") {
17 return (record.toArray() as Array<{ toJSON?: () => Row }>).map((row) =>
18 typeof row.toJSON === "function" ? row.toJSON() : (row as Row)
19 );
20 }
21 if (record.mode === "row") return record.row ? [record.row as Row] : [];
22 if (Array.isArray(record.rows)) return record.rows as Row[];
23 return [];
24};
25
26export type LineChartProps = {
27 label: string;
28 data: unknown;
29 accent?: string;
30};
31
32function LineChart({ label, data, accent = "#ffb232" }: LineChartProps) {
33 const rows = rowsOf(data).map((row) => ({ ...row, fares: Number(row.fares) }));
34 return (
35 <div>
36 <p className='text-xs text-[var(--marketing-text-muted)]'>{label}</p>
37 <div className='mt-2 h-21'>
38 <ResponsiveContainer width='100%' height='100%'>
39 <AreaChart data={rows} margin={{ top: 4, right: 0, bottom: 0, left: 0 }}>
40 <XAxis dataKey='day' hide />
41 <Tooltip
42 formatter={(value) => [usd.format(Number(value)), "Fares"]}
43 labelFormatter={(value) => new Date(String(value)).toLocaleDateString("en-US", { month: "short", day: "numeric" })}
44 contentStyle={{ backgroundColor: "#18181b", border: "1px solid #3f3f46", borderRadius: 8, color: "#f4f4f5", fontSize: 12 }}
45 labelStyle={{ color: "#a1a1aa" }}
46 />
47 <Area
48 type='monotone'
49 dataKey='fares'
50 stroke={accent}
51 strokeWidth={2}
52 fill={accent}
53 fillOpacity={0.18}
54 isAnimationActive={false}
55 />
56 </AreaChart>
57 </ResponsiveContainer>
58 </div>
59 </div>
60 );
61}
62
63export default defineInfigure(LineChart, {
64 label: "Line chart",
65 controls: [{ id: "label", label: "Label", kind: "text", default: "", group: "Content" }],
66 bindings: [
67 {
68 id: "data",
69 label: "Daily fares",
70 path: "data",
71 required: true,
72 kind: "table",
73 cardinality: "many",
74 description: "One row per day, with the fares that day.",
75 rowShape: [{ name: "fares", type: "number", optional: false }],
76 },
77 ],
78});
79

Design tokens

Colours, fonts and spacing are tokens that every block follows.

#ffb232
#f28e2b
geist-sansGeist Sans
h1

Heading

p

Body text, set in the paragraph style.

Export

PDF and PNG exports, embeds for other sites, and a publish link for the whole document.

Export

Publish

Start your first live analytics document.