Cube is the semantic layer for consistent metrics across all BI tools and apps. Data models in YAML or JavaScript: cube("Orders", { sql: "SELECT * FROM orders", measures: { revenue: { sql: "amount", type: "sum" } }, dimensions: { status: { sql: "status", type: "string" } } }). YAML: cubes: [{ name: Orders, sql_table: public.orders, measures: [{ name: revenue, sql: amount, type: sum }], dimensions: [{ name: status, sql: status, type: string }] }]. Joins: joins: { Users: { sql: "${Orders}.user_id = ${Users}.id", relationship: many_to_one } }. Segments: segments: { completed: { sql: "${CUBE}.status = 'completed'" } }. Pre-aggregations: pre_aggregations: { revenue_by_day: { measures: [revenue, order_count], dimensions: [created_date], time_dimension: created_at, granularity: day, partition_granularity: month } }. REST API: POST /cubejs-api/v1/load with { query: { measures: ["Orders.revenue"], dimensions: ["Orders.status"], timeDimensions: [{ dimension: "Orders.created_at", dateRange: "last 30 days" }] } }. GraphQL: query { cube { orders { revenue { value } status { value } } } }. Multi-tenancy: context_to_roles: ({ securityContext }) => [securityContext.tenant_id], row-level security: sql: \… WHERE tenant_id = ’${SECURITY_CONTEXT.tenant_id}‘`. JavaScript client: import cubejs from “@cubejs-client/core”, cubejsApi.load(query).then(resultSet => resultSet.chartPivot())`. Claude Code generates Cube data models, pre-aggregations, API integrations, and multi-tenancy configurations.
CLAUDE.md for Cube
## Cube Stack
- Version: @cubejs-backend/server >= 0.35
- Models: model/*.yml or model/*.js — cubes with sql_table, measures, dimensions, joins
- Pre-aggs: pre_aggregations block in each cube — critical for production performance
- REST: POST /cubejs-api/v1/load — accepts { query: { measures, dimensions, timeDimensions } }
- GraphQL: GET /cubejs-api/v1/graphql — with Cube GraphQL schema
- Auth: JWT token signed with CUBEJS_API_SECRET — { exp, iat, securityContext: {...} }
- Multi-tenancy: contextToAppId + queryRewrite for row-level security
Data Model
# model/cubes/orders.yml — Cube data model for orders
cubes:
- name: orders
sql_table: public.orders
title: Orders
description: All customer orders
joins:
- name: users
sql: "{orders}.user_id = {users}.id"
relationship: many_to_one
- name: products
sql: "{orders}.product_id = {products}.id"
relationship: many_to_one
measures:
- name: count
type: count
title: Order Count
- name: revenue
sql: amount_usd
type: sum
title: Total Revenue
format: currency
- name: avg_order_value
sql: amount_usd
type: avg
title: Avg Order Value
format: currency
- name: refund_rate
sql: "CASE WHEN {status} = 'refunded' THEN 1.0 ELSE 0.0 END"
type: avg
title: Refund Rate
format: percent
- name: unique_buyers
sql: user_id
type: count_distinct
title: Unique Buyers
dimensions:
- name: id
sql: id
type: string
primary_key: true
- name: status
sql: status
type: string
title: Order Status
- name: created_at
sql: created_at
type: time
title: Created At
- name: created_date
sql: "DATE_TRUNC('day', created_at)"
type: time
title: Created Date
segments:
- name: completed
sql: "{orders}.status = 'completed'"
title: Completed Orders
- name: high_value
sql: "{orders}.amount_usd > 100"
title: High Value Orders (>$100)
pre_aggregations:
- name: revenue_by_day
measures:
- revenue
- count
- unique_buyers
dimensions:
- users.plan
- users.country
time_dimension: created_at
granularity: day
partition_granularity: month
refresh_key:
every: 1 hour
build_range_start:
sql: "SELECT NOW() - INTERVAL '90 days'"
build_range_end:
sql: "SELECT NOW()"
- name: revenue_by_status
measures:
- revenue
- count
dimensions:
- status
time_dimension: created_at
granularity: month
Cube Server Configuration
// cube.js — Cube server configuration
const CubejsServer = require("@cubejs-backend/server")
const jwt = require("jsonwebtoken")
const server = new CubejsServer({
checkAuth: (req, auth) => {
if (!auth) throw new Error("No token")
const decoded = jwt.verify(auth, process.env.CUBEJS_API_SECRET!)
req.securityContext = decoded
},
/** Row-level security: filter data by tenant */
queryRewrite: (query, { securityContext }) => {
if (!securityContext?.tenantId) return query
const userFilter = {
member: "users.tenant_id",
operator: "equals",
values: [securityContext.tenantId],
}
return {
...query,
filters: [...(query.filters ?? []), userFilter],
}
},
/** Context for pre-aggregation partitioning per tenant */
contextToAppId: ({ securityContext }) =>
`tenant_${securityContext?.tenantId ?? "shared"}`,
/** Scheduled refresh for pre-aggregations */
scheduledRefreshContexts: async () =>
["tenant_a", "tenant_b", "tenant_c"].map((tenantId) => ({
securityContext: { tenantId },
})),
})
server.listen().then(({ port }) => {
console.log(`Cube server listening on port ${port}`)
})
Next.js Integration
// lib/cube/client.ts — Cube API client for Next.js frontend
import { CubejsApi } from "@cubejs-client/core"
const CUBE_API_URL = process.env.NEXT_PUBLIC_CUBE_API_URL!
let _api: CubejsApi | null = null
export function getCubeApi(token: string): CubejsApi {
const { default: cubejs } = require("@cubejs-client/core")
return cubejs(token, { apiUrl: CUBE_API_URL })
}
export type MetricQuery = {
measures: string[]
dimensions?: string[]
dateRange?: [string, string] | string
granularity?: "day" | "week" | "month" | "quarter" | "year"
filters?: Array<{ member: string; operator: string; values: string[] }>
limit?: number
}
export async function runMetricQuery(
token: string,
query: MetricQuery,
): Promise<Array<Record<string, string | number>>> {
const api = getCubeApi(token)
const cubeQuery: Parameters<CubejsApi["load"]>[0] = {
measures: query.measures,
dimensions: query.dimensions,
filters: query.filters,
limit: query.limit ?? 5000,
...(query.dateRange ? {
timeDimensions: [{
dimension: query.measures[0].split(".")[0] + ".created_at",
dateRange: query.dateRange,
...(query.granularity ? { granularity: query.granularity } : {}),
}],
} : {}),
}
const resultSet = await api.load(cubeQuery)
return resultSet.tablePivot()
}
// app/api/cube-token/route.ts — generate Cube JWT for authenticated users
import { NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import jwt from "jsonwebtoken"
export async function GET() {
const session = await auth()
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
const token = jwt.sign(
{
tenantId: session.user.tenantId,
userId: session.user.id,
role: session.user.role,
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour
},
process.env.CUBEJS_API_SECRET!,
)
return NextResponse.json({ token })
}
For the dbt Semantic Layer alternative when already using dbt Core or dbt Cloud and wanting to define metrics co-located with dbt models using the MetricFlow syntax — dbt Semantic Layer integrates directly into your existing dbt project while Cube is a standalone semantic layer server with its own data model language that can sit on top of any warehouse without requiring dbt. For the Looker/LookML alternative when needing an enterprise BI platform with a 20-year head start in semantic modeling, LookML data language, embedded analytics SDK, and Looker Studio integration — Looker is the de-facto enterprise choice while Cube is open-source, API-first, and better for embedding analytics in custom applications rather than the Looker Explore UI. The Claude Skills 360 bundle includes Cube skill sets covering data models, pre-aggregations, multi-tenancy, and JavaScript client integration. Start with the free tier to try semantic layer generation.