Malloy Documentation
search

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/sdk

Important: 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