Semaphor
Data Apps

Quickstart

Add a governed Semaphor KPI to your own React app with the Data App SDK.

In this quickstart you add a live Revenue KPI to an existing React app. Your UI declares the query, and Semaphor runs it server-side with the same governed execution and security policies as your dashboards.

Before You Start

You need:

  • a React 19 app built with Vite or Next.js;
  • a Semaphor project with a semantic domain, for example a commerce domain with a fact_orders dataset;
  • the project ID and project secret, available to your server (never to the browser).

Install the SDK

npm install react-semaphor

The Data App SDK ships as a subpath of the package:

import {
  SemaphorDataAppProvider,
  SemaphorDevtools,
  semaphor,
  useSemaphorQuery,
} from 'react-semaphor/data-app-sdk';

Mint a project token on your server

Call the Token API from your backend with your project credentials. Identify the end user so Semaphor can apply their tenant and security policies.

server/semaphor-token.ts
export async function mintSemaphorToken() {
  const response = await fetch('https://semaphor.cloud/api/v1/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      type: 'project',
      projectId: process.env.SEMAPHOR_PROJECT_ID,
      projectSecret: process.env.SEMAPHOR_PROJECT_SECRET,
      tenantId: 'tenant_acme',
      endUserEmail: 'maria@acme.com',
    }),
  });

  if (!response.ok) {
    throw new Error(`Token request failed: ${response.status}`);
  }

  const { accessToken } = await response.json();
  return accessToken as string;
}

The browser receives only the short-lived accessToken. See Token Integration for tenant, end-user, and security options.

Keep secrets server-side

Never put your projectSecret or other long-lived credentials in browser code.

Wrap your app in the provider

Every SDK hook runs under SemaphorDataAppProvider. Pass it the token from your server.

Mint the token in a server component and pass it to a client component that holds the provider.

app/analytics/page.tsx
import { mintSemaphorToken } from '@/server/semaphor-token';
import { AnalyticsApp } from './analytics-app';

export default async function AnalyticsPage() {
  const token = await mintSemaphorToken();
  return <AnalyticsApp token={token} />;
}
app/analytics/analytics-app.tsx
'use client';

import { SemaphorDataAppProvider } from 'react-semaphor/data-app-sdk';
import { RevenueKpi } from './revenue-kpi';

export function AnalyticsApp({ token }: { token: string }) {
  return (
    <SemaphorDataAppProvider token={token}>
      <RevenueKpi />
    </SemaphorDataAppProvider>
  );
}

Expose mintSemaphorToken() through an endpoint on your own backend, for example GET /api/semaphor-token, then fetch it when the app loads.

src/App.tsx
import { useEffect, useState } from 'react';
import { SemaphorDataAppProvider } from 'react-semaphor/data-app-sdk';
import { RevenueKpi } from './RevenueKpi';

export function App() {
  const [token, setToken] = useState<string>();

  useEffect(() => {
    fetch('/api/semaphor-token')
      .then((response) => response.json())
      .then((body) => setToken(body.accessToken));
  }, []);

  if (!token) return <div>Connecting...</div>;

  return (
    <SemaphorDataAppProvider token={token}>
      <RevenueKpi />
    </SemaphorDataAppProvider>
  );
}

Define a source and a metric

A source points at a semantic dataset. Fields carry their source, so Semaphor knows which dataset owns each one.

analytics.ts
import { semaphor } from 'react-semaphor/data-app-sdk';

export const orders = semaphor.source.semantic({
  domainId: 'commerce',
  datasetName: 'fact_orders',
});

export const revenue = semaphor.field.measure('revenue', {
  source: orders,
  aggregate: 'SUM',
});

export const revenueKpi = semaphor.metric({
  id: 'revenue_kpi',
  label: 'Revenue',
  source: orders,
  measures: [revenue],
  primaryMeasure: revenue,
});

Use the domainId and datasetName from your own project.

Render it with useSemaphorQuery

RevenueKpi.tsx
import { useSemaphorQuery } from 'react-semaphor/data-app-sdk';
import { revenueKpi } from './analytics';

export function RevenueKpi() {
  const result = useSemaphorQuery(revenueKpi);

  if (result.isLoading) return <div>Loading revenue...</div>;
  if (result.error) return <div>{result.error.message}</div>;

  return (
    <section>
      <h2>Revenue</h2>
      <strong>{result.primaryValue}</strong>
    </section>
  );
}

The hook sends the query to Semaphor, which applies the token's tenant and security policies before it runs. Render the result with any components you like, including your own design system.

Add DevTools in development

SemaphorDevtools shows each query, its inputs, execution status, and diagnostics. Turn on debug outside production and mount one DevTools instance inside the provider.

const isDev = process.env.NODE_ENV !== 'production'; // Vite: import.meta.env.DEV

<SemaphorDataAppProvider token={token} debug={isDev}>
  <RevenueKpi />
  <SemaphorDevtools buttonPosition="bottom-right" panelPosition="right" />
</SemaphorDataAppProvider>;

DevTools render nothing when debug is off, so production users never see them.

Next Steps

On this page