Build custom data applications with the Publisher SDK. Embed live, governed analytics from your Malloy models directly into React apps.
Try the Example App
The fastest way to understand the SDK is to run the example data app:
# Clone the publisher repo and install at the root. The example app resolves # react and @emotion from the repo-root node_modules, so this step is required. git clone https://github.com/malloydata/publisher.git cd publisher bun install # Start the Publisher server, serving the bundled example packages npx @malloy-publisher/server --server_root packages/server # In a new terminal, install and run the example app. It is not part of the # workspace, so it needs its own install as well as the one above. cd examples/data-app bun install bun run dev
Open http://localhost:5173 to see a working dashboard with embedded Malloy visualizations.
The example app demonstrates:
Storefront dashboard – A fixed grid of SDK tiles over the storefront model
Single Embed – Embedding a single query result
Dynamic Dashboard – Adding and arranging widgets at runtime
Interactive – Using raw data with custom Recharts visualizations
Core Concepts
The SDK has two fundamental building blocks. For complete documentation, see the Publisher SDK README.
1. ServerProvider
Wraps your app and connects to a Publisher server:
import { ServerProvider } from "@malloy-publisher/sdk/client"; import "@malloy-publisher/sdk/styles.css"; function App() { return ( <ServerProvider> <YourDashboard /> </ServerProvider> ); }
By default the SDK talks to /api/v0 on the same origin the page is served from. That is what you want when Publisher serves your app itself, and it is why the example app proxies /api/v0 through Vite to port 4000. To point at a different server, pass baseURL:
<ServerProvider baseURL="https://analytics.yourcompany.com/api/v0"> <YourDashboard /> </ServerProvider>
2. EmbeddedQueryResult
Renders a Malloy query result. Results render as a table unless the query or the model carries a renderer tag (e.g. # bar_chart, # line_chart), which is what selects a visualization.
import { EmbeddedQueryResult, createEmbeddedQueryResult, encodeResourceUri, } from "@malloy-publisher/sdk"; function MyChart() { const embeddedQuery = createEmbeddedQueryResult({ query: "run: order_items -> { group_by: products.brand; aggregate: total_sales }", resourceUri: encodeResourceUri({ environmentName: "examples", packageName: "storefront", modelPath: "storefront.malloy", }), }); return <EmbeddedQueryResult embeddedQueryResult={embeddedQuery} />; }
resourceUri is how you address a model: it names the environment, the package, and the model path. It is always required, not only when the server holds more than one package, and createEmbeddedQueryResult throws without it. Instead of writing the query inline, you can reference a view defined in the model by passing queryName together with sourceName.
Installation
npm install @malloy-publisher/sdkImportant: Import the CSS styles in your app entry point:
import "@malloy-publisher/sdk/styles.css";Project Structure
A typical SDK project structure:
my-data-app/ ├── src/ │ ├── main.tsx # App entry with ServerProvider │ ├── Dashboard.tsx # Dashboard with EmbeddedQueryResult │ ├── constants/ │ │ └── widgets.json # Saved embed configurations │ └── components/ │ └── CustomChart.tsx ├── package.json └── vite.config.ts # Proxies /api/v0 to the Publisher server
There is no environment variable that points the SDK at a Publisher server. Either proxy /api/v0 in
vite.config.ts (below) or pass baseURL to ServerProvider.
CORS Configuration
If your React app runs on a different port/domain than Publisher, configure CORS or use a proxy.
Vite proxy example (vite.config.ts):
export default defineConfig({ server: { proxy: { '/api/v0': { target: 'http://localhost:4000', changeOrigin: true, }, }, }, });
Then use relative paths in ServerProvider:
<ServerProvider baseURL="/api/v0">Next Steps
Example App Source – Full working example
REST API – Direct HTTP integration
Explorer – Build queries visually before embedding