Documentation

Add Dashu to your product

Install the packages into your own backend to add administrator-only, natural-language analytics. Your application remains responsible for identity, permissions, database credentials, and persistence.

Embedded SDK

The SDK turns a question into validated, read-only SQL and returns typed rows plus a declarative display plan. Database selection and AI provider selection are independent. PostgreSQL is the currently implemented database adapter.

Request flow: browser → your authorized API route → Dashu core → AI provider for planning → your database for local execution. Database credentials and result rows never go to the AI provider. The filtered schema and question do.

1. Install the packages

This Next.js example uses PostgreSQL, OpenRouter, and the optional React components. Node 20 or newer is required on the backend.

npm install @rophpad/dashu-core @rophpad/dashu-database-postgres @rophpad/dashu-provider-openrouter @rophpad/dashu-next @rophpad/dashu-react
# .env.local — backend only
DASHU_DATABASE_URL=postgresql://dashu_reader:password@host:5432/app
OPENROUTER_API_KEY=sk-or-...

2. Configure Dashu on the server

Create one server-side instance. Use a dedicated PostgreSQL role with onlySELECT on approved views or tables.

// lib/dashu.ts
import { createDashu } from "@rophpad/dashu-core";
import { postgresAdapter } from "@rophpad/dashu-database-postgres";
import { openRouterProvider } from "@rophpad/dashu-provider-openrouter";

export const dashu = createDashu({
  ai: openRouterProvider({
    apiKey: process.env.OPENROUTER_API_KEY!,
    model: "openai/gpt-4.1-mini",
  }),
  dataSources: {
    analytics: postgresAdapter({
      connectionString: process.env.DASHU_DATABASE_URL!,
      schemas: ["analytics"],
    }),
  },
  defaultDataSource: "analytics",
  defaults: {
    maxRows: 200,
    statementTimeoutMs: 10_000,
    exposeSql: false,
    allowExport: false,
    allowSaveDashboard: false,
    denyTables: ["analytics.payment_tokens"],
    denyColumns: ["analytics.customers.email"],
  },
});

3. Mount an authorized route

The browser sends only a question and bounded follow-up history. Resolve the actor, tenant, data source, and policy from your trusted server session—never from request JSON.

// app/api/dashu/ask/route.ts
import { dashuRoute } from "@rophpad/dashu-next";
import { dashu } from "@/lib/dashu";
import { requireCurrentUser } from "@/lib/auth";

export const runtime = "nodejs";

export const POST = dashuRoute(dashu, {
  getActor: async (request) => {
    const user = await requireCurrentUser(request);
    if (!user.permissions.includes("dashu:ask")) return null;

    return {
      id: user.id,
      tenantId: user.tenantId,
      permissions: user.permissions,
    };
  },
  // Select a configured name, not a connection string from the browser.
  selectDataSource: ({ actor }) =>
    actor.tenantId === "internal" ? "analytics" : undefined,
});

Return null fromgetActor to deny access. Hiding the page in the frontend is not authorization.

4. Add the React UI

The hook owns fetch state, cancellation, retry, and up to six follow-up turns. The renderer uses only the validated result contract and never evaluates model-generated HTML.

// app/admin/analytics/AskData.tsx
"use client";

import { DashuComposer, DashuResult, useDashu } from "@rophpad/dashu-react";

export function AskData() {
  const { ask, cancel, result, error, loading } = useDashu({
    endpoint: "/api/dashu/ask",
    keepHistory: true,
  });

  return (
    <section>
      <DashuComposer
        onSubmit={(question) => void ask(question)}
        onCancel={cancel}
        loading={loading}
        suggestions={["Revenue by month", "Top five products this quarter"]}
      />

      {error && <p role="alert">{error.message}</p>}
      {result && (
        <DashuResult
          result={result}
          showSql={result.capabilities.showSql}
        />
      )}
    </section>
  );
}

You can replace individual renderers with your design system through thecomponents prop, or skip@rophpad/dashu-react and renderresult.display yourself.

Choose an AI provider

Swap only the ai value; the database adapter, route, and UI stay unchanged.

// Dashu Managed AI
import { managedProvider } from "@rophpad/dashu-provider-managed";
const ai = managedProvider({
  cloudUrl: "https://dashu.dev",
  credential: process.env.DASHU_INSTALLATION_CREDENTIAL!,
});

// OpenAI-compatible local or private endpoint
import { openAiCompatibleProvider } from "@rophpad/dashu-provider-openai-compatible";
const ai = openAiCompatibleProvider({
  name: "Internal Ollama",
  baseUrl: "http://ollama:11434/v1",
  model: "qwen2.5-coder:7b",
});
Getting a Managed AI credential. Managed AI is the only mode that involves this service, and it needs an active plan. Buy one on the pricing page, then create a credential under your account. It is shown once, so store it as DASHU_INSTALLATION_CREDENTIAL in your backend environment — it is a bearer token and must never reach a browser. Creating a credential with the name of an existing one rotates it.
What Managed AI receives. The question, the filtered schema, and any approved semantic vocabulary. It does not receive your database credential or your query result rows — planning happens here, execution happens in your backend.

Security checklist

  1. 1. Restrict database grants. Use a dedicated read-only identity with access only to approved schemas or views.
  2. 2. Authorize every request. Map your roles to permissions such as dashu:ask, dashu:view-sql, and dashu:export.
  3. 3. Enforce tenant isolation outside the model. Use separate databases, separate schemas, or PostgreSQL row-level security. Do not depend on generated WHERE tenant_id = ....
  4. 4. Keep secrets server-side. Never put database URLs, AI keys, or installation credentials in client components or NEXT_PUBLIC_* variables.
  5. 5. Persist deliberately. The SDK stores nothing. Use onAnswer for history you choose to retain and onEvent for metadata-only observability.

The PostgreSQL adapter also validates SQL, adds a hard row limit, and executes each statement inside a read-only transaction with a timeout. Those controls supplement—not replace—database permissions.

Package reference

@rophpad/dashu-coreRequest-scoped planning, policy, validation, execution, and result contracts.
@rophpad/dashu-database-postgresPostgreSQL introspection, SQL guard, and read-only execution.
@rophpad/dashu-nextAuthorized Next.js route handlers for questions, saved SQL, and schemas.
@rophpad/dashu-reactQuestion state, composer, tables, metrics, and charts for React 18+.
@rophpad/dashu-provider-openrouterUse an OpenRouter API key owned by your organization.
@rophpad/dashu-provider-openai-compatibleConnect Ollama, vLLM, LocalAI, or another compatible endpoint.
@rophpad/dashu-provider-managedRoute planning through Dashu Managed AI with an installation credential.

Advanced APIs include dashu.run()for re-validating and replaying saved SQL, dashu.schema()for an approved schema browser, and matchingdashuRunRoute anddashuSchemaRoute Next.js handlers.

Read the full Embedded SDK architecture →

Data and privacy

Database credentials and query result rows stay in your environment. With an external or managed AI provider, the question and filtered schema are sent for query planning.

The cloud site stores account, purchase, installation, quota, and usage metadata. It never needs your database connection string. Provider-specific retention and privacy terms still apply when you choose OpenRouter or another hosted endpoint.

Need managed AI or additional product capabilities? See plans or get in touch.