Skip to main content
Data apps are source-authored React applications that compile to a single HTML file and run inside a Doc. They go beyond standard YAML dashboards by giving you full control: client-side filtering, interactive pivot tables, custom charts, and rich component library.

Starter repo

Clone the starter template with build tooling, runtime, and examples

Component reference

Full props documentation for all UI components

When to use data apps vs YAML dashboards

YAML DashboardData App
Standard charts, tables, KPIsCustom interactive UI (expandable rows, conditional formatting)
Multiple tiles in a grid layoutSingle full-screen experience
Cube/SQL datasets with built-in vizCustom JavaScript visualizations (ECharts, Perspective.js)
Quick to build with FiSource-authored React with component library
No build step requiredCompiled to single HTML via build tool
Use data apps when you need: client-side filtering and drill-down, multi-tab layouts, interactive Perspective grids, ECharts visualizations, or fully custom UI. Use YAML dashboards when: standard charts, tables, and KPIs are sufficient. They’re faster to build, easier to maintain, and Fi can create them from natural language.

How data flows

app.json          Definite platform       Browser DuckDB WASM        App.tsx
(manifest)   -->  (server-side fetch) --> (local tables)        -->  (client-side SQL)
  1. app.json declares every data resource the app needs. Each resource has a key, a kind, and a source (SQL, Cube, or file).
  2. The Definite platform reads the manifest and fetches data server-side from DuckLake, Cube, or GCS. The app never talks to the warehouse directly.
  3. The runtime loads the fetched data into a browser-side DuckDB WASM instance as local tables. kind: "dataset" resources become DuckDB tables; kind: "json" resources are returned as plain arrays.
  4. App.tsx queries those local tables via useSqlQuery(dataset, sql, deps). These queries run in the browser against DuckDB WASM, not against the server.
Column names in your useSqlQuery SQL must match the aliases in your app.json SQL. If app.json has SELECT foo AS myColumn, the local table column is myColumn. Mismatches cause “Binder Error: Referenced column not found” at runtime.

Quick start

Clone the starter repo and create a new app:
git clone https://github.com/definite-app/definite-data-apps.git
cd definite-data-apps
make setup                     # install dependencies
make new-app NAME=my-app       # create from blank template
This creates examples/my-app/ with:
examples/my-app/
  app.json              # Manifest (declare your data resources here)
  src/
    main.tsx            # Entry point (boilerplate)
    App.tsx             # Your UI code
    definite-runtime.tsx  # Runtime library + components

Step 1: Define your data in app.json

Declare the data resources your app needs:
{
  "version": 2,
  "name": "Sales Dashboard",
  "entry": "src/main.tsx",
  "resources": {
    "sales": {
      "kind": "dataset",
      "source": {
        "type": "sql",
        "sql": "SELECT region AS region, STRFTIME(order_date, '%Y-%m-%d') AS orderDate, amount::DOUBLE AS amount FROM LAKE.PUBLIC.orders LIMIT 200000"
      },
      "public": false
    },
    "regions": {
      "kind": "json",
      "source": {
        "type": "sql",
        "sql": "SELECT region_id AS regionId, region_name AS regionName FROM LAKE.PUBLIC.regions ORDER BY 2"
      },
      "public": false
    }
  }
}
Always use type: "sql" with camelCase column aliases. This gives you full control over column names. Avoid type: "cube" because Cube responses use long title-based column names that are painful to work with.

Step 2: Build your UI in App.tsx

import React from "react";
import {
  useDataset, useSqlQuery, useTheme,
  AppShell, Card, KpiCard, LoadingState, ErrorState,
} from "./definite-runtime";

export default function App() {
  const { theme, toggleTheme } = useTheme();
  const data = useDataset("sales");

  const kpis = useSqlQuery(
    data,
    data.tableRef
      ? `SELECT COUNT(*)::INTEGER AS orders, SUM(amount)::INTEGER AS revenue FROM ${data.tableRef}`
      : "",
    [],
  );

  if (data.loading) return <LoadingState message="Loading sales data..." />;
  if (data.error) return <ErrorState title="Load Error" message={data.error} />;

  return (
    <AppShell title="Sales Dashboard" theme={theme} onToggleTheme={toggleTheme}>
      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        <KpiCard title="Total Orders" value={kpis.data?.[0]?.orders} format="number" loading={kpis.loading} />
        <KpiCard title="Revenue" value={kpis.data?.[0]?.revenue} format="currency" loading={kpis.loading} />
      </div>
    </AppShell>
  );
}

Step 3: Build

make build NAME=my-app
This produces examples/my-app/dist/index.html. The build validates that all useDataset() and useJsonResource() calls reference keys that exist in app.json.

Step 4: Deploy

Upload dist/index.html to Definite Drive (via the MCP server or UI) and create a Doc with a full-screen HTML tile:
version: 1
schemaVersion: "2025-01"
kind: dashboard
metadata:
  name: "Sales Dashboard"
datasets: {}
layout:
  columns: 36
  tiles:
    - id: app
      x: 0
      y: 0
      w: 36
      h: 22
      type: html
      fullScreen: true
      driveFile: "apps-v2/my-app/dist/index.html"

Manifest resources

Resource kinds

KindHookUse for
datasetuseDataset(key)Data loaded into browser DuckDB WASM as a queryable table
jsonuseJsonResource(key)Small lookup lists returned as plain arrays (dropdowns, config)

Source types

TypeDescription
sqlSQL executed server-side against DuckLake. Recommended.
duckdbFileA .duckdb file downloaded from Drive/GCS and attached locally
cubeCube semantic model query. Not recommended for data apps.

Public embeds

For publicly shared Docs, resources need a snapshot block:
"snapshot": {
  "format": "json",
  "drivePath": "apps-v2/my-app/snapshots/data.json"
}
Public mode uses cached/snapshotted data only. No live query execution.

Runtime hooks

The runtime library provides React hooks for data loading and querying:
HookReturnsPurpose
useDataset(key)DatasetHandleLoad dataset into browser DuckDB, get tableRef for SQL
useSqlQuery(dataset, sql, deps)QueryState<T>Run client-side SQL against loaded dataset
useJsonResource(key)QueryState<T>Load JSON resource as array
useTheme(){ theme, toggleTheme }Dark/light mode
usePerspective(dataset){ client, perspectiveTable }Initialize Perspective viewer for a dataset
All hooks that load data (useDataset, useJsonResource) cache results in IndexedDB with a 24-hour TTL. Call refresh() on the returned handle for a hard refresh.

UI components

The runtime includes a full component library. See the component reference for detailed props.
CategoryComponents
LayoutAppShell, Card, TabGroup
Data displayKpiCard, DataTable, ReportTable, Badge
ChartsEChart, PerspectivePanel
InputsSelect, MultiSelect, FilterPills, TextInput, DateInput
FeedbackLoadingState, ErrorState, Tooltip, ResourceCacheBadge

Best practices

  1. Always use SQL resources (type: "sql") over Cube resources for data apps. You control column names via aliases.
  2. Alias columns to camelCase in app.json SQL. Convert dates with STRFTIME(col, '%Y-%m-%d').
  3. Keep client-side SQL simple. Use app.json SQL for joins, CASE WHEN, and complex filters. Use useSqlQuery only for GROUP BY, SUM of pre-computed columns, and date range filters.
  4. Cast SUM results to ::INTEGER in client-side SQL. DuckDB WASM may return HUGEINT which JavaScript can’t handle cleanly.
  5. Pre-compute conditional flags in app.json SQL. DuckDB WASM has known issues with compound CASE WHEN expressions (they silently return 0).

Caching

The runtime caches data loads in IndexedDB with a 24-hour TTL. Cache keys include the resource key, mode, and manifest definition, so rebuilt apps invalidate automatically. Use ResourceCacheBadge in your AppShell meta slot to show cache status and provide a “Clear cache & reload” button.

Next steps

Starter Repo

Full starter template with build tooling, examples, and component reference

Agent Reference

Programmatically create data apps via the MCP server or AI agents

Tile Types Reference

Configuration options for all tile types including HTML tiles