Define semantic layer cubes with Drizzle ORM tables, including dimensions, measures, time dimensions, and security context. Use when creating analytics cubes, defining data models, setting up multi-tenant filtering, or working with drizzle-cube semantic layers.
Scanned 9/12/2026
Install to Claude Code
npx -y skills add aibot88/sec_skill_store --skill cube-definition --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Cube Definition?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/aibot88-cube-definition)More formats (shields.io, HTML) on the badges page.
---
name: cube-definition
description: Define semantic layer cubes with Drizzle ORM tables, including dimensions, measures, time dimensions, and security context. Use when creating analytics cubes, defining data models, setting up multi-tenant filtering, or working with drizzle-cube semantic layers.
---
# Drizzle Cube Definition
This skill helps you create semantic layer cubes using Drizzle Cube's `defineCube` function. Cubes provide a business-friendly abstraction over database tables with type-safe dimensions, measures, and built-in security.
## Core Concept
A **cube** in Drizzle Cube is:
- A semantic layer over one or more database tables
- Defined using Drizzle ORM table references
- **Always filtered by security context** (mandatory for multi-tenant isolation)
- Type-safe with full TypeScript support
## Basic Cube Structure
```typescript
import { defineCube } from 'drizzle-cube'
import { eq } from 'drizzle-orm'
import { employees } from './schema' // Your Drizzle schema
export const employeesCube = defineCube('Employees', {
title: 'Employee Analytics', // Optional human-readable title
description: 'Analytics for employee data', // Optional description
// MANDATORY: Security context filtering for multi-tenant isolation
sql: (ctx) => ({
from: employees,
where: eq(employees.organisationId, ctx.securityContext.organisationId)
}),
// Define dimensions (categorical/time fields for grouping/filtering)
dimensions: {
id: {
title: 'Employee ID',
type: 'number',
sql: () => employees.id,
primaryKey: true // Mark the primary key
},
name: {
title: 'Employee Name',
type: 'string',
sql: () => employees.name
},
email: {
title: 'Email Address',
type: 'string',
sql: () => employees.email
},
departmentId: {
title: 'Department',
type: 'number',
sql: () => employees.departmentId
},
isActive: {
title: 'Active Status',
type: 'boolean',
sql: () => employees.isActive
},
createdAt: {
title: 'Created Date',
type: 'time',
sql: () => employees.createdAt
}
},
// Define measures (aggregated numeric values)
measures: {
count: {
title: 'Total Employees',
type: 'count',
sql: () => employees.id
},
totalSalary: {
title: 'Total Salary',
type: 'sum',
sql: () => employees.salary
},
avgSalary: {
title: 'Average Salary',
type: 'avg',
sql: () => employees.salary
},
minSalary: {
title: 'Minimum Salary',
type: 'min',
sql: () => employees.salary
},
maxSalary: {
title: 'Maximum Salary',
type: 'max',
sql: () => employees.salary
}
}
})
```
**Important**: The `defineCube` function takes two parameters:
1. **name** (string) - The cube name (e.g., 'Employees')
2. **definition** (object) - The cube configuration (sql, dimensions, measures, etc.)
## Dimension Types
Drizzle Cube supports four dimension types:
### 1. String Dimensions
```typescript
dimensions: {
name: {
title: 'Full Name',
type: 'string',
sql: () => employees.name
},
email: {
type: 'string',
sql: () => employees.email
}
}
```
### 2. Number Dimensions
```typescript
dimensions: {
id: {
type: 'number',
sql: () => employees.id,
primaryKey: true
},
departmentId: {
type: 'number',
sql: () => employees.departmentId
}
}
```
### 3. Time Dimensions
```typescript
dimensions: {
createdAt: {
title: 'Created Date',
type: 'time',
sql: () => employees.createdAt
},
updatedAt: {
type: 'time',
sql: () => employees.updatedAt
}
}
```
### 4. Boolean Dimensions
```typescript
dimensions: {
isActive: {
title: 'Active',
type: 'boolean',
sql: () => employees.isActive
},
isRemote: {
title: 'Remote Worker',
type: 'boolean',
sql: () => employees.isRemote
}
}
```
## Measure Types
Drizzle Cube supports several aggregation types:
### 1. Count Measures
```typescript
measures: {
count: {
title: 'Total Count',
type: 'count',
sql: () => employees.id // Column to count
},
activeCount: {
title: 'Active Employees',
type: 'count',
sql: () => employees.id,
filters: [(ctx) => eq(employees.isActive, true)] // Filtered count
}
}
```
### 2. Count Distinct Measures
```typescript
measures: {
uniqueDepartments: {
title: 'Unique Departments',
type: 'countDistinct',
sql: () => employees.departmentId
}
}
```
### 3. Sum Measures
```typescript
measures: {
totalSalary: {
title: 'Total Salary',
type: 'sum',
sql: () => employees.salary
}
}
```
### 4. Average Measures
```typescript
measures: {
avgSalary: {
title: 'Average Salary',
type: 'avg',
sql: () => employees.salary
}
}
```
### 5. Min/Max Measures
```typescript
measures: {
minSalary: {
title: 'Minimum Salary',
type: 'min',
sql: () => employees.salary
},
maxSalary: {
title: 'Maximum Salary',
type: 'max',
sql: () => employees.salary
}
}
```
### 6. Calculated Measures
```typescript
measures: {
salaryPercentage: {
title: 'Salary as Percentage',
type: 'calculated',
calculatedSql: '{totalSalary} / NULLIF({departmentBudget}, 0) * 100'
}
}
```
## SQL Property Patterns
The `sql` property in dimensions and measures can be defined in two ways:
### 1. Direct Column Reference (Recommended)
```typescript
dimensions: {
name: {
type: 'string',
sql: () => employees.name // Function returning column
}
}
```
### 2. Direct Column (Also Valid)
```typescript
dimensions: {
name: {
type: 'string',
sql: employees.name // Direct column reference
}
}
```
**Best Practice**: Use the function form `() => employees.column` for consistency and to access the QueryContext if needed.
## Advanced Patterns
### Filtered Measures
Add filters to measures for conditional aggregation:
```typescript
measures: {
activeEmployees: {
title: 'Active Employees',
type: 'count',
sql: () => employees.id,
filters: [
(ctx) => eq(employees.isActive, true)
]
},
seniorEmployees: {
title: 'Senior Employees',
type: 'count',
sql: () => employees.id,
filters: [
(ctx) => {
const { gte } = ctx.imports
return gte(employees.yearsOfService, 5)
}
]
},
highEarners: {
title: 'High Earners',
type: 'count',
sql: () => employees.id,
filters: [
(ctx) => {
const { gt } = ctx.imports
return gt(employees.salary, 100000)
}
]
}
}
```
### Computed Dimensions
Use SQL expressions for computed values:
```typescript
import { sql } from 'drizzle-orm'
dimensions: {
fullName: {
title: 'Full Name',
type: 'string',
sql: () => sql`${employees.firstName} || ' ' || ${employees.lastName}`
},
seniorityLevel: {
title: 'Seniority',
type: 'string',
sql: () => sql`CASE
WHEN ${employees.yearsOfService} < 2 THEN 'Junior'
WHEN ${employees.yearsOfService} < 5 THEN 'Mid-level'
ELSE 'Senior'
END`
}
}
```
## Security Context (MANDATORY)
**Every cube MUST filter by security context** to ensure multi-tenant data isolation:
```typescript
// ✅ CORRECT - Security context filtering
sql: (ctx) => ({
from: employees,
where: eq(employees.organisationId, ctx.securityContext.organisationId)
})
// ✅ CORRECT - Multiple security conditions
sql: (ctx) => ({
from: employees,
where: and(
eq(employees.organisationId, ctx.securityContext.organisationId),
eq(employees.tenantId, ctx.securityContext.tenantId)
)
})
// ❌ WRONG - No security filtering (data leak!)
sql: (ctx) => ({
from: employees
// Missing where clause - SECURITY VIOLATION
})
```
**Note**: The `sql` function receives a `QueryContext` object (abbreviated as `ctx`), which contains:
- `ctx.securityContext` - The security context with tenant/organization information
- `ctx.imports` - Drizzle ORM operators and functions
## Complete Example
```typescript
import { defineCube } from 'drizzle-cube'
import { eq, sql, and, gte } from 'drizzle-orm'
import { employees } from './schema'
export const employeesCube = defineCube('Employees', {
title: 'Employee Analytics',
description: 'Comprehensive employee data and metrics',
// Security context filtering (MANDATORY)
sql: (ctx) => ({
from: employees,
where: eq(employees.organisationId, ctx.securityContext.organisationId)
}),
dimensions: {
id: {
title: 'Employee ID',
type: 'number',
sql: () => employees.id,
primaryKey: true
},
name: {
title: 'Name',
type: 'string',
sql: () => employees.name
},
email: {
title: 'Email',
type: 'string',
sql: () => employees.email
},
department: {
title: 'Department',
type: 'string',
sql: () => employees.departmentName
},
isActive: {
title: 'Active',
type: 'boolean',
sql: () => employees.isActive
},
createdAt: {
title: 'Hire Date',
type: 'time',
sql: () => employees.createdAt
},
// Computed dimension
seniorityLevel: {
title: 'Seniority Level',
type: 'string',
sql: () => sql`CASE
WHEN ${employees.yearsOfService} < 2 THEN 'Junior'
WHEN ${employees.yearsOfService} < 5 THEN 'Mid-level'
ELSE 'Senior'
END`
}
},
measures: {
count: {
title: 'Total Employees',
type: 'count',
sql: () => employees.id
},
activeCount: {
title: 'Active Employees',
type: 'count',
sql: () => employees.id,
filters: [(ctx) => eq(employees.isActive, true)]
},
totalSalary: {
title: 'Total Salary',
type: 'sum',
sql: () => employees.salary
},
avgSalary: {
title: 'Average Salary',
type: 'avg',
sql: () => employees.salary
},
minSalary: {
title: 'Minimum Salary',
type: 'min',
sql: () => employees.salary
},
maxSalary: {
title: 'Maximum Salary',
type: 'max',
sql: () => employees.salary
},
uniqueDepartments: {
title: 'Unique Departments',
type: 'countDistinct',
sql: () => employees.departmentId
},
// Filtered measure
seniorEmployees: {
title: 'Senior Employees',
type: 'count',
sql: () => employees.id,
filters: [(ctx) => gte(employees.yearsOfService, 5)]
}
}
})
```
## Registering Cubes
Once defined, register cubes with the semantic layer compiler:
```typescript
import { SemanticLayerCompiler } from 'drizzle-cube'
import { drizzle } from 'drizzle-orm/postgres-js'
import { employeesCube } from './cubes/employees'
const db = drizzle(process.env.DATABASE_URL)
const compiler = new SemanticLayerCompiler({
drizzle: db,
schema: schema
})
// Register your cube
compiler.registerCube(employeesCube)
```
## Best Practices
1. **Always include security context filtering** - This is mandatory for multi-tenant isolation
2. **Use meaningful names** - Cube names and dimension/measure keys should be clear and descriptive
3. **Add titles** - Provide human-readable titles for UI display
4. **Mark primary keys** - Set `primaryKey: true` on ID dimensions
5. **Type safety** - Use Drizzle ORM table references for compile-time validation
6. **Filtered measures** - Use filters for conditional aggregations instead of creating separate cubes
7. **Use function form for sql** - Prefer `sql: () => column` over direct `sql: column` for consistency
## Common Pitfalls
- **Wrong defineCube signature** - Remember: name is first parameter, NOT inside the object
```typescript
// ❌ WRONG
defineCube({ name: 'Employees', sql: ... })
// ✅ CORRECT
defineCube('Employees', { sql: ... })
```
- **Missing security context** - Every cube must filter by security context
- **Wrong SQL syntax** - Use Drizzle ORM operators (eq, and, or), not raw SQL strings
- **Incorrect types** - Ensure dimension/measure types match the actual data types
- **Missing imports** - Import necessary operators from drizzle-orm
- **Redundant name fields** - Don't add `name:` property to dimensions/measures (the key IS the name)
## Next Steps
- Learn about **cube joins** with the `cube-joins` skill
- Build **queries** using your cubes with the `queries` skill
- Set up **server APIs** with the `server-setup` skill
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!