Metabase Module — Architecture
Overview
The Metabase module (apps/backend/src/metabase/) manages Metabase dashboard configurations and generates JWT-signed embed URLs for multi-tenant dashboard embedding in the FlowPOS platform.
It is not the Metabase server itself — it's the backend layer that:
- Stores dashboard metadata (code, name, description, business scoping)
- Maps dashboards to environment-specific Metabase dashboard IDs (staging vs production)
- Controls which roles and business modules can see which dashboards
- Associates dashboards with UI forms for page-level embedding
- Generates secure JWT embed URLs with locked
business_idparameters
Domain Entities
| Entity | Table | Purpose |
|---|---|---|
| Dashboard | metabase_dashboard | Core entity — human-readable code, name, description, optional business scoping |
| Dashboard Instance | metabase_dashboard_instance | Maps a dashboard to a numeric Metabase dashboard ID per environment (staging/production) |
| Dashboard Module | metabase_dashboard_module | Associates dashboards with business modules (e.g. inventory, sales) |
| Dashboard Form | metabase_dashboard_form | Associates dashboards with UI form paths for page-level embedding |
| Dashboard Role | metabase_dashboard_role | RBAC — controls which user roles can access which dashboards |
Architecture (Hexagonal)
domain/
├── metabase-dashboard-repository.domain.ts # Dashboard repository port
├── metabase-dashboard-instance-repository.domain.ts # Instance repository port
├── metabase-dashboard-module-repository.domain.ts # Module repository port
├── metabase-dashboard-form-repository.domain.ts # Form repository port
├── metabase-dashboard-role-repository.domain.ts # Role repository port
└── metabase-embedding.types.ts # EmbedUrlParams interface
application/
└── metabase-dashboard.service.ts # Orchestrates all use cases
infrastructure/
├── metabase-dashboard.repository.ts # Kysely adapter for dashboards
├── metabase-dashboard-instance.repository.ts # Kysely adapter for instances
├── metabase-dashboard-module.repository.ts # Kysely adapter for modules
├── metabase-dashboard-form.repository.ts # Kysely adapter for forms
├── metabase-dashboard-role.repository.ts # Kysely adapter for roles
└── metabase-embedding.service.ts # JWT signing + URL generation
interfaces/
├── metabase.controller.ts # HTTP routes
├── dtos/ # Request DTOs with validation
└── query/ # Pagination/sorting query objects
Key Use Cases
1. Dashboard Embed URL Generation
The primary use case. Given a dashboard code and a business ID:
- Look up the dashboard by code
- Verify the dashboard is active and accessible to the business
- Check role-based access (if user role is available)
- Resolve the environment-specific Metabase dashboard ID (staging vs production, based on
DEPLOY_ENV) - Sign a JWT with
{ resource: { dashboard: <id> }, params: { business_id: <uuid> } } - Return the full embed URL:
<METABASE_SITE_URL>/embed/dashboard/<jwt>#bordered=true&titled=true
The embed URL endpoint also accepts numeric Metabase dashboard IDs directly for backward compatibility.
2. Dashboard CRUD
Standard CRUD for managing dashboard metadata. Dashboards are identified by:
- UUID (
id) — primary key - Code (
code) — human-readable slug (e.g.sales-overview)
3. Sub-entity Management
Each sub-entity (instance, module, form, role) follows a standard pattern:
- List all for a dashboard
- Get one by composite key (dashboard + secondary key)
- Create (with uniqueness check)
- Update
- Delete
API Endpoints
Dashboard CRUD
| Method | Path | Description |
|---|---|---|
POST | /reports/dashboard | Create dashboard |
GET | /reports/dashboard | List dashboards (paginated, filterable) |
GET | /reports/dashboard/code/:code | Get by code |
GET | /reports/dashboard/:id | Get by UUID |
PATCH | /reports/dashboard/:id | Update dashboard |
DELETE | /reports/dashboard/:id | Delete dashboard |
Embed URL
| Method | Path | Description |
|---|---|---|
GET | /reports/dashboard/:code/embed-url?businessId=<uuid> | Generate embed URL (accepts code or numeric ID) |
Dashboard Instances
| Method | Path | Description |
|---|---|---|
GET | /reports/dashboard/:id/instances | List instances |
GET | /reports/dashboard/:id/instance/:environment | Get instance |
POST | /reports/dashboard/:id/instance/:environment | Create instance |
PATCH | /reports/dashboard/:id/instance/:environment | Update instance |
DELETE | /reports/dashboard/:id/instance/:environment | Delete instance |
Dashboard Modules
| Method | Path | Description |
|---|---|---|
GET | /reports/dashboard/:id/modules | List modules |
GET | /reports/dashboard/:id/module/:moduleId | Get module |
POST | /reports/dashboard/:id/module/:moduleId | Create module |
PATCH | /reports/dashboard/:id/module/:moduleId | Update module |
DELETE | /reports/dashboard/:id/module/:moduleId | Delete module |
Dashboard Forms
| Method | Path | Description |
|---|---|---|
GET | /reports/dashboard/by-form/:formPath | Get dashboards by form path |
GET | /reports/dashboard/:id/forms | List forms |
GET | /reports/dashboard/:id/form/:formId | Get form |
POST | /reports/dashboard/:id/form/:formId | Create form |
PATCH | /reports/dashboard/:id/form/:formId | Update form |
DELETE | /reports/dashboard/:id/form/:formId | Delete form |
Dashboard Roles
| Method | Path | Description |
|---|---|---|
GET | /reports/dashboard/:id/roles | List roles |
GET | /reports/dashboard/:id/role/:roleName | Get role |
POST | /reports/dashboard/:id/role/:roleName | Create role |
PATCH | /reports/dashboard/:id/role/:roleName | Update role |
DELETE | /reports/dashboard/:id/role/:roleName | Delete role |
Configuration
Required environment variables:
| Variable | Description | Default |
|---|---|---|
METABASE_SITE_URL | Metabase instance base URL | required |
METABASE_EMBED_SECRET_KEY | Secret key for JWT signing | required |
METABASE_TOKEN_EXPIRY_SECONDS | JWT token lifetime | 3600 (1 hour) |
DEPLOY_ENV | Determines which instance environment to use | — |
Design Decisions
-
Single embed URL endpoint — Instead of separate routes for code vs numeric ID, the
GET /reports/dashboard/:code/embed-urlendpoint auto-detects the identifier type. Numeric values are treated as direct Metabase IDs; strings are resolved as dashboard codes. -
Environment-based instance resolution — Dashboards map to different Metabase dashboard IDs per environment. The
DEPLOY_ENVenv var determines which instance to use at embed URL generation time. -
Role-based access as data — Instead of hardcoding role checks in code, role access is stored in the
metabase_dashboard_roletable. This allows runtime configuration of which roles can see which dashboards. -
Form-path association — Dashboards are associated with UI form paths (e.g.
/sales,/inventory) via themetabase_dashboard_formjoin table. This enables page-level dashboard embedding in the frontend.