SDK Concepts
Understand the Data App SDK building blocks before writing queries.
This page explains the objects you will see in every Data App. If you are new to analytics apps, start here before writing a large dashboard.
The Five Building Blocks
| Building block | What it means | Example |
|---|---|---|
| Source | A semantic dataset that owns fields. | fact_orders, dim_campaign |
| Field | A source-bearing measure, dimension, date, or id. | revenue, campaign_name, order_date |
| Query | A server-side analytics request. | KPI, records, matrix, analysis, input options |
| Input | A user-controlled filter or control. | Campaign dropdown, date range, top-N selector |
| Result | Rows, columns, status, pagination, and diagnostics. | records, columns, executionResult |
The SDK is intentionally source-bearing. A field is not just "revenue". It is "revenue" from fact_orders.
Sources
A source points to a semantic dataset.
import { semaphor } from 'react-semaphor/data-app-sdk';
export const orders = semaphor.source.semantic({
domainId: 'commerce',
datasetName: 'fact_orders',
});
export const campaign = semaphor.source.semantic({
domainId: 'commerce',
datasetName: 'dim_campaign',
});Use the semantic datasetName from your Semaphor project. Add datasetId when available to strengthen identity, but do not omit datasetName. The same Data App can query multiple sources when the semantic model defines relationships between them.
Fields
Fields tell Semaphor what each column means.
export const revenue = semaphor.field.measure('revenue', {
source: orders,
aggregate: 'SUM',
});
export const campaignName = semaphor.field.dimension('campaign_name', {
source: campaign,
});
export const campaignId = semaphor.field.id('campaign_id', {
source: campaign,
});
export const orderDate = semaphor.field.date('order_date', {
source: orders,
});Use these public roles:
measure: a value Semaphor aggregates, such as revenue, quantity, cost, or active users.dimension: a grouping or display field, such as campaign name, region, category, or segment.date: a date or timestamp used for time windows and trends.id: a stable identifier used as an option value or relationship key.
Measure, not metric
The SDK contract uses measure for numeric fields. People may say "metric" conversationally, but authored SDK fields should use measure.
Queries
Queries are created with the semaphor builder.
const revenueKpi = semaphor.metric({
id: 'revenue_kpi',
label: 'Revenue',
source: orders,
measures: [revenue],
primaryMeasure: revenue,
});
const revenueByCampaign = semaphor.records({
id: 'revenue_by_campaign',
label: 'Revenue by Campaign',
source: orders,
fields: [campaignName, revenue],
limit: 10,
});The query source is the base grain. In revenueByCampaign, the source is fact_orders because revenue comes from orders. The campaign name is a related projection from dim_campaign.
Inputs
Inputs are user-controlled values. A filter input points at a field.
const campaignInput = useSemaphorInput({
id: 'campaign',
label: 'Campaign',
kind: 'filter',
field: campaignId,
operator: 'in',
multi: true,
});Pass input handles to queries:
const result = useSemaphorQuery(revenueByCampaign, {
inputs: [campaignInput],
});The handle carries the active value, the field, operator, and relationship hints if the input needs them.
Input Options
Dropdown choices are also queries. This keeps large option lists server-side.
const campaignOptions = semaphor.inputOptions({
id: 'campaign_options',
inputId: 'campaign',
source: campaign,
labelField: campaignName,
valueField: campaignId,
searchField: campaignName,
limit: 100,
});labelField is what the user sees. valueField is the stable value stored in input state.
Results
All query hooks return status fields:
const result = useSemaphorQuery(revenueByCampaign);
if (result.isLoading) return <LoadingState />;
if (result.error) return <ErrorState error={result.error} />;Records and SQL queries return rows and columns:
result.records;
result.columns;
result.rowCount;
result.pagination;Metric queries return values:
result.value;
result.measures;
result.delta;
result.deltaPercent;Matrix queries return a matrix result:
result.matrixResult;
result.grid;Analysis queries can return named result sets:
result.resultSets?.changes?.records;
result.resultSets?.drivers?.records;
result.analysisWarnings;Every governed result may also include executionResult, which is where Semaphor surfaces validation status, relationship diagnostics, SQL metadata, row counts, coverage, partial/failure status, and authoritative lineage.
The Most Important Rule
The frontend declares analytical intent. Semaphor executes it.
Do not do these in client code:
- join fact and dimension rows;
- filter large fact tables in the browser;
- infer relationships by matching field names;
- build SQL strings for normal semantic questions.
Do these instead:
- use source-bearing refs;
- use semantic query builders;
- pass input handles to queries;
- inspect typed diagnostics when relationships are missing or ambiguous.