Semantic Layer

Your metrics are lying.
Fix that.

Six BI tools. Twelve definitions of "revenue." Zero alignment.

The Semantic Layer ends the chaos. Define once. Trust everywhere.

semantic-layer.yaml
cubes:
  - name: orders
    measures:
      - name: revenue
        type: sum
        sql: amount
      - name: count
        type: count
    dimensions:
      - name: status
        type: string
SQL
REST
GraphQL

One Truth

Define metrics once in code. Every tool gets the same answer.

Code-First

YAML models. Git versioned. PR reviews for metric changes.

Sub-Second Queries

Pre-aggregations and smart caching. Dashboards finally load fast.

Built-in Security

Row-level, column-level security. Works with your existing RLS.

We had 47 different definitions of 'active user' across our BI tools. The Semantic Layer paid for itself in the first week by eliminating reconciliation meetings.
Head of DataSeries C Fintech

Metrics as code. Not spreadsheets.

Define cubes that map to your tables. Declare measures and dimensions in YAML. Version control everything. When Finance asks "what changed?", show them the git diff.

Define Cubes

Map business entities to database tables with measures and dimensions

Declare Measures

Sum, count, average, or custom SQL calculations—defined once, used everywhere

Add Dimensions

Group and filter your data with type-safe dimension definitions

Version Control

Your metric definitions are code. Review changes in PRs like any other code.

Query your way. We don't judge.

Sub-second queries at any scale. Pre-aggregations handle the heavy lifting.

Pre-aggregations

Materialize expensive queries automatically. Define once, refresh on schedule.

Smart Routing

Queries automatically served from cache when possible.

Partitioning

Time-based partitions for efficient historical queries.

Works with your entire stack

cubes:
  - name: orders
    sql_table: public.orders

    measures:
      - name: total_revenue
        type: sum
        sql: amount

      - name: order_count
        type: count

      - name: avg_order_value
        type: number
        sql: "{total_revenue} / NULLIF({order_count}, 0)"

    dimensions:
      - name: status
        type: string
        sql: status

      - name: created_at
        type: time
        sql: created_at
pre_aggregations:
  - name: orders_daily
    measures:
      - total_revenue
      - order_count
    dimensions:
      - status
    time_dimension: created_at
    granularity: day
    refresh_key:
      every: 1 hour

    # Automatic partitioning
    partition_granularity: month

    # Smart invalidation
    build_range_start:
      sql: SELECT DATE_SUB(NOW(), 2 YEAR)
    build_range_end:
      sql: SELECT NOW()
cubes:
  - name: orders
    sql_table: public.orders

    # Row-level security
    access_policy:
      sql: "org_id = {SECURITY_CONTEXT.org_id}"

    measures:
      - name: total_revenue
        type: sum
        sql: amount
        # Column-level security
        public: false
        meta:
          requires_role: analyst

      - name: profit_margin
        type: number
        sql: "(revenue - cost) / revenue"
        meta:
          requires_role: finance
cubes:
  - name: orders
    sql_table: public.orders

    # Row-level security
    access_policy:
      sql: "org_id = {SECURITY_CONTEXT.org_id}"

    measures:
      - name: total_revenue
        type: sum
        sql: amount
        # Column-level security
        public: false
        meta:
          requires_role: analyst

      - name: profit_margin
        type: number
        sql: "(revenue - cost) / revenue"
        meta:
          requires_role: finance
pre_aggregations:
  - name: orders_daily
    measures:
      - total_revenue
      - order_count
    dimensions:
      - status
    time_dimension: created_at
    granularity: day
    refresh_key:
      every: 1 hour

    # Automatic partitioning
    partition_granularity: month

    # Smart invalidation
    build_range_start:
      sql: SELECT DATE_SUB(NOW(), 2 YEAR)
    build_range_end:
      sql: SELECT NOW()