Acumatica · Cube

Cube.js Patterns — A Field Guide

Cube.js Patterns — A Field Guide is the work that turns raw data into decisions. The pipeline from "we have data" to "we have a model that runs in production" is the same in every.

John Kihiu12 min read

Cube is a semantic layer: a place to define metrics, dimensions, and joins once, in one modelling layer, so every dashboard, BI tool, and ad-hoc query downstream computes "revenue" or "active users" the same way instead of each consumer reimplementing the SQL slightly differently. The value shows up the first time two dashboards built by two different teams stop disagreeing about a number that should be the same.

The core idea: cube schema

A Cube schema defines a "cube" per fact table (or logical entity) with measures (aggregations — sums, counts, averages) and dimensions (things you group or filter by). Once defined, Cube generates SQL from a higher-level query — you ask for a measure grouped by a dimension over a time range, and Cube compiles that into the actual SQL against your warehouse, handling the join logic so consumers never write raw SQL against the underlying tables directly.

JAVASCRIPT · CUBE SCHEMA DEFINITION
cube('Orders', {
  sql: `SELECT * FROM public.orders`,

  measures: {
    count: { type: 'count' },
    totalRevenue: { sql: 'amount', type: 'sum' },
  },

  dimensions: {
    status: { sql: 'status', type: 'string' },
    createdAt: { sql: 'created_at', type: 'time' },
  },

  joins: {
    Customers: {
      relationship: 'belongsTo',
      sql: `${CUBE}.customer_id = ${Customers}.id`,
    },
  },
});

Pre-aggregations are the performance lever

Querying a raw fact table with millions of rows for every dashboard load is slow and expensive on the warehouse. Cube's pre-aggregation feature lets you define a rollup — a smaller, pre-computed summary table, refreshed on a schedule — that Cube automatically routes matching queries to instead of hitting the raw table. A dashboard asking for daily revenue by region can be served from a pre-aggregated daily-by-region rollup in milliseconds instead of scanning the full orders table on every page load. Getting pre-aggregations right (which measures, which dimensions, what refresh cadence) is usually the single biggest lever on both query latency and warehouse cost.

Pre-aggregations trade freshness for speed — make that trade-off explicit

A pre-aggregation refreshed hourly means dashboards built on it are up to an hour stale. That's fine for most business dashboards and wrong for an operational view that needs near-real-time numbers. Set refresh cadence per pre-aggregation based on what each specific metric actually needs, not one global default.

One model, many consumers

Cube exposes the modelled semantic layer over multiple APIs — a SQL API that BI tools (Tableau, Looker Studio, Power BI) can connect to as if Cube were just another Postgres-compatible database, a REST/GraphQL API for custom dashboards, and native integrations for embedding charts directly in an application. The pattern this enables: business logic like "what counts as an active customer" or "how do we calculate churn" gets defined once in the Cube schema, and every consumer — the exec dashboard, the customer-facing usage widget, the ad-hoc analyst query — inherits the same definition instead of each team encoding it independently and drifting apart over time.

Access control at the semantic layer

Because every query routes through Cube, row-level and column-level security can be enforced centrally rather than duplicated in every dashboard tool's own permission system. A multi-tenant SaaS product can inject the current tenant ID into the security context and have Cube automatically scope every query to that tenant, which removes an entire class of "someone forgot to add the WHERE clause in this one dashboard" data leak.

Start by migrating your most-duplicated metric, not your whole warehouse

The fastest way to prove Cube's value is to find the one metric that's currently defined slightly differently in three different dashboards — revenue, active users, whatever it is — model it once in Cube, and point all three consumers at that single definition. That concrete "the numbers finally match" moment is a stronger case for broader adoption than modelling the entire schema upfront.

LayerWithout a semantic layerWith Cube
Metric definitionsDuplicated per dashboard, drift over timeDefined once, consumed everywhere
Query performanceEvery dashboard hits raw tablesPre-aggregations serve common queries fast
Access controlEnforced per-tool, inconsistentlyEnforced centrally in the security context
New BI tool onboardingRe-derive all metric logic from scratchConnect to Cube's SQL API, inherit existing models

Wrapping up

Cube earns its place when the same metric is being recomputed slightly differently across multiple dashboards and tools — that's the specific pain it's designed to remove. Model one high-value, currently-duplicated metric first, get pre-aggregations tuned for its actual freshness needs, and expand the schema from there rather than trying to migrate an entire warehouse's worth of metrics into it up front.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.