DataBrowser can render either static rows passed as props or a host-owned resource selected by resourceId. It never asks the model for raw SQL, Firestore paths, collection names, endpoints, secrets, or authorization decisions.
Core concepts#
A resource is an allowlisted business object the model may ask to browse, such as orders, customers, or invoices. It defines the safe product contract: labels, columns, filters, sort options, row actions, and model-facing usage guidance.
A driver is the adapter that knows how to fetch a resource. Fable UI ships optional REST and Firebase drivers, and host apps can register their own drivers for SQL, internal APIs, or other systems.
The registry stores drivers, resources, and optional resource runtimes. The exported fableRegistry is a per-runtime singleton: browser and server bundles each get their own module instance. Use it as the default path unless you have a reason to isolate resources with new DataSourceRegistry().
FableDataProvider is a client context bridge. It passes the registry and per-session context to DataBrowser hooks, such as orgId, tenantId, locale, and signal. Do not put Firebase Auth in this context; Firebase Auth belongs on the Firebase driver registration.
Install#
pnpm dlx shadcn@latest add shobky/fable-ui/data-browserOnly install drivers you need:
pnpm dlx shadcn@latest add shobky/fable-ui/rest-driverpnpm dlx shadcn@latest add shobky/fable-ui/firebase-driverTypical setup#
Keep resource definitions pure, then register drivers and resources before rendering the chat or DataBrowser surface.
import { defineResource } from "@/lib/fable-ui/core"
export const ordersResource = defineResource({
id: "orders",
label: "Orders",
entityLabel: "orders",
driver: "firebase",
source: { collection: "orgs/{orgId}/orders", requireAuth: true },
columns: [
{ key: "orderNumber", label: "Order #" },
{ key: "customer.name", label: "Customer" },
{ key: "status", label: "Status" },
],
})"use client"
import { fableRegistry } from "@/lib/fable-ui/core"
import { createFirebaseDriver } from "@/lib/fable-ui/drivers/firebase"
import { auth, db } from "@/lib/firebase"
import { ordersResource } from "./resources"
fableRegistry.registerDriver("firebase", createFirebaseDriver({ db, auth }))
fableRegistry.registerResource(ordersResource)
export { fableRegistry }Provider placement#
Wrap the smallest app area that renders Fable tools or resource-backed DataBrowser components. In Next.js App Router, FableDataProvider is a Client Component and can be rendered from a layout, page, or client shell.
"use client"
import * as React from "react"
import { FableDataProvider } from "@/lib/fable-ui/core"
import "@/lib/fable-ui/data/client-registry"
export function ChatShell({
orgId,
children,
}: {
orgId: string
children: React.ReactNode
}) {
const context = React.useMemo(() => ({ orgId }), [orgId])
return <FableDataProvider context={context}>{children}</FableDataProvider>
}Register drivers and resources before a resource-backed DataBrowser renders. The provider may use the default singleton registry, or you may pass a custom registry with <FableDataProvider registry={registry}>.
Prompting#
Give the model the safe resource manifest, not private connection details:
import { describeAvailableResources } from "@/lib/fable-ui/tools/show-data-browser-tool"
const system = [
"Use show_data_browser for browsable business data.",
describeAvailableResources(),
].join("\n\n")The model selects a registered resourceId. At render time, DataBrowser asks the registry for that resource, the registry resolves its driver, and the driver fetches rows using host-owned configuration and context.