Semaphor
Semantic Domains

Admin Setup Guide

Complete guide for administrators to create and configure semantic domains

  1. Log in to Semaphor
  2. Select your project from the project list
  3. Click on "Domains" tab in the top navigation

Creating a Domain

Step 1: Create a New Domain

  1. On the Domains page, click the "Create Domain" button
  2. Fill in the domain details:
    • Name: A unique identifier (e.g., sales_analytics)
    • Label: User-friendly display name (e.g., Sales Analytics)
    • Description: Brief explanation of what this domain covers
    • Connection: Select the data connection this domain uses
  3. Click "Create" to save your domain

Quick Setup Summary

Before diving into the details, here's the complete workflow at a glance:

  1. Create Domain → Name it and select a connection
  2. Add Datasets → Select tables, let AI generate labels (30-60s per dataset)
  3. Discover Relationships → Run AI discovery, accept suggestions (1-2 minutes)
  4. Save & Test → Save domain, test in a dashboard card

Total time: 5-10 minutes for a typical domain with 4-5 datasets

The AI does most of the work! Semaphor automatically generates field labels, descriptions, and discovers relationships between your datasets. Your main job is reviewing and accepting suggestions.


Now let's walk through each step in detail.

Step 2: Open the Domain Editor

After creating your domain, you'll be taken to the Domain Editor, which has three tabs:

  • Datasets: Add and configure your data sources, fields, metrics, and context
  • Relationships: Define how datasets connect
  • Template: View and edit the domain as YAML/JSON

Adding Datasets

After creating your domain, you'll add the datasets (tables) you want users to explore.

Step 1: Select Your Datasets

  1. In the Domain Editor, ensure you're on the Datasets tab
  2. Click the "Add Dataset" button
  3. Choose one of three modes: Tables (default, multi-select physical tables), Data Model (reference an existing data model), or SQL Dataset (author a SQL-backed virtual dataset). See Virtual Datasets for the SQL Dataset walkthrough.
  4. Select the tables or views you want to include from your database
  5. Click "Add" to add each dataset

AI-Powered Labels: After adding datasets, Semaphor automatically generates user-friendly labels, field names, and descriptions using AI. This process takes a few moments but saves you from manually configuring each field.

Step 2: Wait for AI Processing

After you add datasets and click Save, Semaphor's AI automatically:

  • Generates customer-facing labels for table names (e.g., orders_tbl → "Orders")
  • Creates readable field names (e.g., cust_id → "Customer ID")
  • Adds helpful descriptions for fields
  • Classifies fields as identifiers (keys), facts (quantities to aggregate), or dimensions (attributes to group by)
  • Suggests appropriate default aggregates for facts (SUM, COUNT, AVG)

Note: This AI operation typically takes 30-60 seconds per dataset. You'll see a loading indicator while processing.

Adding Datasets

Step 3: Review AI-Generated Fields (Optional)

After AI processing completes, you can review and adjust the generated labels:

  1. Click the "Edit" button (pencil icon) next to a dataset
  2. Review the AI-generated field labels and descriptions
  3. Make adjustments if needed (most users find the AI labels work well as-is)
  4. Hide any fields that shouldn't be visible to end users

Advanced: You can also create virtual datasets using custom SQL queries. See the Advanced Configuration section below.


That's all you need to get your datasets ready! The AI handles the labeling and classification automatically. Next, you'll define how these datasets relate to each other.

Creating Relationships

Relationships define how datasets connect to each other, enabling automatic joins when users explore data across multiple datasets.

Recommended Workflow: Use AI Discovery to automatically identify relationships. Most users find they don't need to manually define any relationships after using AI discovery.

Using AI-Powered Relationship Discovery

Semaphor can automatically discover potential relationships between your datasets using AI analysis of field names, data types, and semantic patterns. This is the fastest way to set up your domain.

The relationship discovery process typically takes 1-2 minutes to complete.

The entire process takes just a few steps:

  1. Click "Discover Relationships" in the Relationships tab
  2. Click "Run Discovery" and wait 1-2 minutes while AI analyzes your datasets
  3. Review the suggestions - each shows confidence (High/Medium/Low) and rationale
  4. Select relationships to add - use "Select High Confidence Only" or review each one
  5. Click "Accept Selected" - done!

Discover Relationships

What the AI analyzes:

  • Field names (e.g., customer_id likely joins to customers.id)
  • Data types (matching types for join keys)
  • Semantic patterns (fact tables to dimension tables)
  • Table structures (primary/foreign key patterns)

What you'll see in suggestions:

  • From → To: Which datasets connect, many side first (e.g., orders → customers)
  • Join Fields: Which fields match (e.g., orders.customer_id = customers.id)
  • Confidence: High, Medium, or Low
  • Rationale: Why the AI thinks this relationship exists
  • Cardinality: Many-to-one, one-to-one, or many-to-many

Tip: Start by accepting only High Confidence relationships. You can always run discovery again or manually add relationships later.


That's it! After accepting relationships, your domain is ready for users to query. The relationships enable automatic joins when users select fields from multiple datasets.

Advanced: Manual Relationship Management

Note: Most users don't need to manually create relationships after using AI Discovery. This section is for advanced use cases.

If you need to manually add or edit a relationship:

  1. In the Relationships tab, click "Add Relationship"
  2. Fill in the relationship details:
    • Name: Identifier (e.g., orders_to_customers)
    • From dataset: The many side, such as orders
    • To dataset: The side whose fields identify one row, such as customers
    • Match field: Which fields to match on each side
    • Cardinality: Many-to-one, one-to-one, or many-to-many
    • Default join: INNER, LEFT, RIGHT, or FULL
  3. Check Enable automatic join resolution to allow automatic joins
  4. Click "Save"

If the To fields aren't a key of the To dataset, the save shows a warning: joins through the relationship may repeat rows. The Relationships tab shows the same warning under the relationship. If those fields do identify one row, click Mark ... as a unique key to declare it. See Join Keys.

When to manually add relationships:

  • AI didn't detect a relationship you know exists
  • You need to specify a specific join type (e.g., INNER vs LEFT)
  • You're working with non-standard naming conventions
  • You need composite key relationships (multiple join fields)

Testing Your Domain

Step 1: Save Your Changes

After configuring datasets and relationships:

  1. Click the "Save" button in the top-right corner
  2. Wait for the confirmation message
  3. Your changes are now persisted

Step 2: Review Validation

When you save, Semaphor validates the domain:

  • Errors block the save and say what to change (for example, an aggregate placed in Facts)
  • Warnings don't block the save (for example, a relationship whose To fields don't identify one row)

Step 3: Test in a Dashboard

  1. Create a new dashboard or open an existing one
  2. Add a new card
  3. Select "Semantic Domain" as the data source
  4. Choose your domain from the dropdown
  5. Try selecting fields from different datasets
    • If relationships are configured correctly, joins happen automatically
    • If fields from unrelated datasets are selected, you'll see an error

Advanced Configuration

Advanced Dataset Configuration

Creating Virtual Datasets

Virtual datasets are SQL-backed semantic datasets stored directly in your domain template. Use them for pre-aggregated data, reshaping, or complex joins encoded once and reused across dashboards.

In Add Dataset, switch to the SQL Dataset mode (alongside Tables and Data Model) to pick an owning connection, write SQL in a Monaco editor, run Preview Columns to discover fields, and save. Both subqueries and CTEs are supported.

See Virtual Datasets for the full walkthrough, including SQL examples, editing flow, maintenance actions (Preview Columns, Fetch New Columns, Sync Data Types), and how to handle drift.

Manual Field Configuration

If you need to manually adjust field properties beyond what AI generates:

  1. Click "Edit" on a dataset
  2. For each field, you can modify:
    • Label: User-friendly display name
    • Description: Help text explaining the field
    • Data Type: String, Number, Date, DateTime, Boolean, and more (the database's exact type is kept alongside)
    • Role: Identifier (a key), Fact (aggregatable), or Dimension (groupable)
    • Default Aggregate: SUM, AVG, COUNT, MIN, MAX, and more, for facts
    • Context: Instructions, other names, and example questions for agents
    • Visibility: Show/hide from users (eye icon)

Best practices:

  • Use clear labels: "Order Date" instead of "ord_dt"
  • Add descriptions to help users understand fields
  • Mark dimensions correctly for proper grouping
  • Set default aggregates for facts
  • Add context with the names your users actually say (for example, "sales" for revenue)

Domain Template Schema

Every semantic domain is stored as a template that defines datasets, relationships, metrics, and context. Understanding the schema helps with advanced configuration, version control, and CI/CD pipelines.

For a step-by-step tutorial on writing templates by hand, including how they translate into SQL queries, see the Template Guide.

Template Structure

schemaVersion: "2.1"
aiContext: {...}             # Context for agents (optional)
datasets: [...]              # Datasets with identifiers, facts, and dimensions
relationships: [...]         # How datasets join
metrics: [...]               # Dataset metrics and composed metrics
composedDimensions: [...]    # Domain-level row expressions (optional)

Templates saved in the 2.0 shape

Domains saved before version 2.1 keep working. They read as 2.1, and they are stored as 2.1 the next time you save. In the editor, Review upgrade lists what moves (for example, columns to Facts), any relationships to check, and can read each field's exact database type before you save. The API still accepts 2.0 bodies and upgrades them on save.

Dataset Schema

Each dataset in the datasets array has this structure:

FieldTypeRequiredDescription
namestringYesUnique identifier for the dataset
labelstringYesUser-friendly display name
descriptionstringNoHelp text for users
aiContextstring | objectNoContext for agents
sourceobjectYesWhere rows come from: kind (table or sql), connectionId, connectionType, and schema/table or sql
primaryKeystring[]NoField names that identify one row
uniqueKeysstring[][]NoOther field-name lists that identify one row
fieldsobjectYesField definitions (see below)

Fields Object:

fields:
  identifiers:    # Primary/foreign key fields
    - name: id
      label: "Order ID"
      dataType: number
      sourceField: id
      type: primary
  facts:          # Row-level quantities to aggregate
    - name: amount
      label: "Amount"
      dataType: number
      sourceField: amount
      defaultAggregate: SUM
  dimensions:     # Attributes to group and filter by
    - name: order_date
      label: "Order Date"
      dataType: date
      sourceField: order_date
      granularity: day

Relationship Schema

Each relationship in the relationships array:

FieldTypeRequiredDescription
namestringYesUnique identifier
from{ dataset, fields }YesThe many side
to{ dataset, fields }YesThe side whose fields identify one row
cardinalityenumYesmany_to_one, one_to_one, many_to_many
defaultJoinTypeenumYesINNER, LEFT, RIGHT, FULL
isAutoJoinbooleanYesEnable automatic join resolution
discoveredByenumYesuser, auto, fk_constraint, ai
isActivebooleanYesWhether the relationship is active

Metric Schema

Metrics are stored in one metrics list. A dataset metric aggregates one dataset; a composed metric combines named inputs:

metrics:
  - name: profit_margin
    dataset: orders
    label: "Profit Margin"
    description: "Profit as percentage of revenue"
    expression: "(SUM(amount) - SUM(cost)) / NULLIF(SUM(amount), 0)"
    dataType: number
    format:
      type: percentage
      decimals: 2
    tags:
      - finance
      - kpi

  - name: net_revenue
    label: "Net Revenue"
    expression: "{sales} - COALESCE({returned}, 0)"
    inputs:
      sales: { dataset: orders, field: amount, aggregate: SUM }
      returned: { dataset: returns, field: amount, aggregate: SUM }
FieldTypeRequiredDescription
namestringYesMetric name
datasetstringDataset metricsThe dataset the metric aggregates
inputsobjectComposed metricsInput references, each naming its dataset
labelstringNoDisplay name
descriptionstringNoHelp text
aiContextstring | objectNoContext for agents
expressionstringYesThe calculation
dataTypeenumDataset metricsLogical type, usually number
formatobjectNoDisplay formatting options
tagsstring[]NoCategorization tags

Composed Dimension Schema

Domain-level row expressions are stored in composedDimensions. New groupings usually belong on a dataset as a dimension with an expression; composed dimensions keep working for existing domains and for grain mappings.

composedDimensions:
  - name: order_month
    label: "Order Month"
    description: "Monthly time grouping"
    expression: "DATE_TRUNC('month', {order_date})"
    inputs:
      order_date:
        dataset: orders
        field: order_date
    dataType: date
FieldTypeRequiredDescription
namestringYesDimension name
labelstringYesDisplay name
descriptionstringNoHelp text
expressionstringYesSQL expression with {input} tokens
inputsobjectYesInput field references
dataTypeenumNostring, number, boolean, date, datetime

Direct Template Editing

Power users can edit the domain template directly as YAML or JSON. This is useful for:

  • Bulk edits: Rename multiple fields at once
  • Version control: Store templates in Git for change tracking
  • CI/CD pipelines: Automate domain updates as part of deployments
  • Migration: Copy configurations between environments

For the complete field-by-field tutorial, see the Template Guide. For programmatic template generation (AI agents, scripts), see the Agent Guide.

To manage templates via the REST API, see the Domain Templates API.

Opening the Template Tab

  1. Open your domain in the Domain Editor
  2. Click the Template tab
  3. View and edit the full template as YAML or JSON
  4. Click Save to apply changes

Template edits are validated when you save, the same way as edits in the other tabs. Errors block the save; warnings, such as a relationship that doesn't join on a key, are shown but don't block it. Test in a dashboard after changing relationships and metrics.

Complete Template Example

Here's a full domain template for a sales analytics domain:

schemaVersion: "2.1"

aiContext:
  instructions: "Sales analytics for the online store. Amounts are in USD."
  examples:
    - "What was average order value by customer segment last quarter?"

datasets:
  - name: orders
    label: "Orders"
    description: "Customer order transactions"
    source:
      kind: table
      connectionId: "conn_abc123"
      connectionType: PostgreSQL
      schema: public
      table: orders
    primaryKey:
      - id
    fields:
      identifiers:
        - name: id
          label: "Order ID"
          dataType: number
          sourceField: id
          type: primary
        - name: customer_id
          label: "Customer ID"
          dataType: number
          sourceField: customer_id
          type: foreign
      dimensions:
        - name: order_date
          label: "Order Date"
          dataType: date
          sourceField: order_date
          granularity: day
        - name: status
          label: "Status"
          dataType: string
          sourceField: status
        - name: order_size_bucket
          label: "Order Size"
          description: "Categorizes orders by amount"
          dataType: string
          sourceField: amount
          expression: |
            CASE
              WHEN amount > 1000 THEN 'Large'
              WHEN amount > 100 THEN 'Medium'
              ELSE 'Small'
            END
        - name: fiscal_quarter
          label: "Fiscal Quarter"
          description: "Fiscal quarter based on order date"
          dataType: string
          expression: "CONCAT('Q', EXTRACT(QUARTER FROM order_date), ' ', EXTRACT(YEAR FROM order_date))"
      facts:
        - name: amount
          label: "Amount"
          dataType: number
          sourceField: amount
          defaultAggregate: SUM
        - name: cost
          label: "Cost"
          dataType: number
          sourceField: cost
          defaultAggregate: SUM
        - name: quantity
          label: "Quantity"
          dataType: number
          sourceField: quantity
          defaultAggregate: SUM

  - name: customers
    label: "Customers"
    source:
      kind: table
      connectionId: "conn_abc123"
      connectionType: PostgreSQL
      schema: public
      table: customers
    primaryKey:
      - id
    fields:
      identifiers:
        - name: id
          label: "Customer ID"
          dataType: number
          sourceField: id
          type: primary
      dimensions:
        - name: segment
          label: "Customer Segment"
          dataType: string
          sourceField: segment
        - name: region
          label: "Region"
          dataType: string
          sourceField: region

relationships:
  - name: orders_to_customers
    from:
      dataset: orders
      fields:
        - customer_id
    to:
      dataset: customers
      fields:
        - id
    cardinality: many_to_one
    defaultJoinType: LEFT
    isAutoJoin: true
    discoveredBy: ai
    confidence: high
    isActive: true

metrics:
  - name: average_order_value
    dataset: orders
    label: "Average Order Value"
    description: "Average revenue per order"
    expression: "SUM(amount) / NULLIF(COUNT(id), 0)"
    dataType: number
    format:
      type: currency
      decimals: 2
      currency: USD
    tags:
      - kpi
      - sales
    aiContext:
      synonyms: [AOV, basket size]

  - name: profit_margin
    dataset: orders
    label: "Profit Margin %"
    description: "Profit as percentage of revenue"
    expression: "(SUM(amount) - SUM(cost)) / NULLIF(SUM(amount), 0) * 100"
    dataType: number
    format:
      type: percentage
      decimals: 1
    tags:
      - kpi
      - finance

Domain-Level Calculated Fields

Add reusable metrics and calculated dimensions directly to your domain template. These fields become available to all users across all dashboards that use the domain.

Adding Metrics

Define metrics in the metrics list of your template. A metric that combines datasets uses named inputs:

metrics:
  - name: conversion_rate
    label: "Conversion Rate"
    description: "Percentage of visitors who made a purchase"
    expression: "{purchases} / NULLIF({visitors}, 0) * 100"
    inputs:
      purchases:
        dataset: orders
        field: id
        aggregate: COUNT
      visitors:
        dataset: sessions
        field: visitor_id
        aggregate: DISTINCT
    format:
      type: percentage
      decimals: 2
    tags:
      - marketing
      - kpi
    aiContext:
      instructions: "Use for questions about how well traffic converts to orders."

Adding Calculated Dimensions

Add a calculated dimension on its dataset, as a dimension with an expression:

datasets:
  - name: customers
    fields:
      dimensions:
        - name: customer_lifetime_tier
          label: "Customer Lifetime Tier"
          description: "Customer value tier based on total spend"
          dataType: string
          sourceField: total_spend
          expression: |
            CASE
              WHEN total_spend > 10000 THEN 'Platinum'
              WHEN total_spend > 5000 THEN 'Gold'
              WHEN total_spend > 1000 THEN 'Silver'
              ELSE 'Bronze'
            END

Best Practices for Domain Fields

  1. Document thoroughly: Include descriptions for every field. Users rely on this context.
  2. Use consistent naming: Follow a pattern like metric_name or dimension_category.
  3. Add tags: Group related fields (e.g., finance, marketing, operations).
  4. Handle edge cases: Use NULLIF for division, COALESCE for null values.
  5. Test before publishing: Validate expressions with sample data.

Domain-level calculated fields appear in the Explorer with a Domain badge, distinguishing them from card-scoped or dashboard-scoped fields.


Next Steps

Once your semantic domain is configured:

  1. Train your users: Share the User Guide with end users
  2. Monitor usage: Track which relationships are used most frequently
  3. Gather feedback: Ask users which fields or relationships they need
  4. Optimize performance: Review query logs for slow multi-hop joins
  5. Expand gradually: Add new datasets and relationships based on user needs
  6. Automate with the API: Use the Semantic Domains API and Domain Templates API to manage domains programmatically

On this page