Semaphor
Data Apps

Filters & Inputs

Add filters, controls, and option lists.

Inputs let users change what a Data App shows. The two main input types are:

  • filters, which constrain data, such as Campaign, Region, State, or Date Range;
  • controls, which change query shape, such as time grain, selected measure, or top-N limit.

This page focuses on filters and option lists.

Filter Input

A filter input points at a field.

const campaignInput = useSemaphorInput({
  id: 'campaign',
  label: 'Campaign',
  kind: 'filter',
  field: campaignId,
  operator: 'in',
  multi: true,
});

Pass the input handle to a query:

const result = useSemaphorQuery(revenueByCampaign, {
  inputs: [campaignInput],
});

If campaignInput is active, Semaphor applies it server-side.

Option Query

Dropdowns need option values. Use semaphor.inputOptions.

const campaignOptions = semaphor.inputOptions({
  id: 'campaign_options',
  inputId: 'campaign',
  source: campaign,
  labelField: campaignName,
  valueField: campaignId,
  searchField: campaignName,
  limit: 100,
});

The user sees labelField. The input stores valueField.

labelField: campaign_name -> "Holiday Retargeting"
valueField: campaign_id   -> "cmp_123"

Prefer stable ids as values. Labels can change or duplicate.

Same-Source Cascading Filters

Use the same source for simple hierarchy filters. For example, Region and State live in dim_geo.

const geo = semaphor.source.semantic({
  domainId: 'commerce',
  datasetName: 'dim_geo',
});

const regionId = semaphor.field.id('region_id', { source: geo });
const regionName = semaphor.field.dimension('region_name', { source: geo });
const stateId = semaphor.field.id('state_id', { source: geo });
const stateName = semaphor.field.dimension('state_name', { source: geo });

Region options:

const regionOptions = semaphor.inputOptions({
  id: 'region_options',
  inputId: 'region',
  source: geo,
  labelField: regionName,
  valueField: regionId,
});

State options:

const stateOptions = semaphor.inputOptions({
  id: 'state_options',
  inputId: 'state',
  source: geo,
  labelField: stateName,
  valueField: stateId,
});

By default, option dependencies are auto: compatible active inputs narrow each other. If Region is South, State options only show southern states. If State is Texas, Region options can narrow to South.

No automatic value propagation

Cascading filters narrow option lists. Selecting Texas should not automatically select South. It can make South the only Region option, but it should not change another input value unless the user does it.

Clearing Stale Child Values

If Region changes from Northeast to South, a previously selected State such as New York is stale. Use useClearInvalidSemaphorInputValue after the child option query succeeds.

import {
  useClearInvalidSemaphorInputValue,
  useSemaphorInput,
  useSemaphorQuery,
} from 'react-semaphor/data-app-sdk';

function StateFilter() {
  const stateInput = useSemaphorInput({
    id: 'state',
    label: 'State',
    kind: 'filter',
    field: stateId,
    operator: 'in',
    multi: true,
  });

  const options = useSemaphorQuery(stateOptions);

  useClearInvalidSemaphorInputValue(stateInput, options);

  return <StateSelect input={stateInput} options={options.options} />;
}

Only a successful option result is authoritative. Loading or failed option queries should not clear a valid persisted value.

Date Range Filter

A date range is a filter with operator: 'between'.

const dateRange = useSemaphorInput({
  id: 'date_range',
  label: 'Date Range',
  kind: 'filter',
  field: orderDate,
  operator: 'between',
  defaultValue: ['2026-01-01', '2026-01-31'],
});

For multi-source dashboards, use one date range input but bind each view to its own modeled date field. For example:

orders view  -> fact_orders.order_date
tickets view -> fact_support_tickets.created_date
usage view   -> fact_product_usage.usage_date

The planner or generated app should use the primary date field from the semantic model when available.

Shared Inputs Across Views

One visible input can drive multiple queries. Keep the same user-facing input, then bind it to the field each query should use.

const dateRangeInput = useSemaphorInput({
  id: 'date_range',
  label: 'Date Range',
  kind: 'filter',
  field: purchaseDate,
  operator: 'between',
});

const purchaseTrend = semaphor.records({
  source: purchaseLine,
  fields: [purchaseDate, netPurchaseValue],
  inputs: [
    semaphor.bindInput(dateRangeInput, {
      field: purchaseDate,
      operator: 'between',
    }),
  ],
});

const salesTrend = semaphor.records({
  source: salesLine,
  fields: [saleDate, salesValue],
  inputs: [
    semaphor.bindInput(dateRangeInput, {
      field: saleDate,
      operator: 'between',
    }),
  ],
});

Use the same pattern for conformed dimension filters. For example, one Material Family input can filter both purchase and sales facts when each query provides the appropriate source-bearing field and relationship hint.

Keep the visible input stable

semaphor.bindInput(...) does not create another control. It maps the same input value to the field and relationship that a specific query needs.

Searchable High-Cardinality Options

For customers, SKUs, users, invoices, or orders, do not load all options.

const customerOptions = semaphor.inputOptions({
  id: 'customer_options',
  inputId: 'customer',
  source: customer,
  labelField: customerName,
  valueField: customerId,
  searchField: customerName,
  limit: 50,
});

Pass a search string from your combobox to the option query when your UI supports typeahead.

Controls

Controls change query shape instead of filtering rows.

const grainControl = useSemaphorInput({
  id: 'grain',
  label: 'Time Grain',
  kind: 'control',
  role: 'grain',
  defaultValue: 'month',
  options: ['day', 'week', 'month', 'quarter'],
});

Common control roles:

RoleExample
grainday, week, month, quarter
measurerevenue, gross margin, quantity
dimensioncampaign, region, category
aggregationsum, average, count
sqlParamexplicit parameter for a SQL fallback query

Input Design Checklist

  • Use ids for valueField when possible.
  • Use labels for labelField.
  • Add searchField for large lists.
  • Add disambiguationFields when labels can duplicate.
  • Let option dependencies default to auto unless you intentionally need independent or explicit.
  • Use server-side option queries. Do not fetch all options into the client for filtering.

On this page