Reference

Feature flags

Feature access in OpsDash is controlled by two systems that work together: plates (the entitlement ladder — free → studio → sales → growth → full_loop → agency, resolved from subscriptions.plan, optionally extended by add-on packs) and the feature_flags table (per-org toggles with role restrictions). Each feature declares a single minPlate — the lowest plate that unlocks it — in packages/config/src/defaults.ts.

How feature access works

Every protected feature passes through four layers:

  1. Plate check: Is the org's plate at or above the feature's minPlate (plus any required add-on pack)?
  2. Subscription status: Must be active or trialing
  3. Feature flag: feature_flags.enabled — is it enabled for this org?
  4. Role check: Is the user's role in feature_flags.allowed_roles?

The feature_flags table

One row per feature per org. Seeded from defaultFeatures on org creation.

feature_flags (
  id          uuid PK,
  org_id      uuid FK → organizations,
  feature_key text,          -- e.g. 'crm:contacts'
  enabled     boolean,       -- on/off for this org
  allowed_roles text[],      -- NULL = all roles; array = restrict
  created_at  timestamptz
)

UI: Settings → Admin → Feature Flags (owner/admin only)

Server-side functions

FunctionLocationPurpose
canAccessFeature()org-context.tsxClient: sidebar, command palette, route guards
requireFeatureAccess()admin.ts → admin-feature-gates.tsServer actions: returns error if no access
ensureFeatureAccess()admin.ts → admin-feature-gates.tsLayouts: redirects to dashboard if no access
ensureTieredFeatureAccess()admin.ts → admin-feature-gates.tsLayouts: plate-gated sub-features (e.g. crm:export layout)
checkFeature()admin.ts → admin-feature-gates.tsSubscription/entitlement check only — no role check
checkUsageLimit()helpers.tsUsage-capped features — checks count vs. plan limit

The plate ladder

Six plates, strictly nested — every plate includes everything below it. Public self-serve pricing shows four columns (Sales is a dormant internal rung; Agency is a “Contact us” lane):

  • Free — $0
  • Studio — $24/mo
  • Sales — $39/mo (dormant; not publicly marketed)
  • Growth — $59/mo
  • Full Loop — $99/mo
  • Agency — $179/mo, sold as “Contact us”

Three flat-rate add-on packs extend a plate without changing it: AI Pack ($25/mo, requires Studio+), Advanced Analytics ($19/mo, Full Loop+), and e-Invoicing / Peppol ($29/mo, Growth+). Keys marked PLANNED below are reserved (wired for gating) but not yet shipped.

All feature keys

CRM

KeyMin plateNotes
crm:contactsFreeUsage-capped (250 on Free → unlimited on Agency).
crm:companiesFreeUsage-capped (50 on Free → unlimited on Agency).
crm:activitiesFreeActivity log.
crm:contact-fieldsFreeStandard contact fields.
crm:gdpr-exportFreePer-contact data export.
crm:dealsSalesUsage-capped pipeline deals.
crm:leadsSalesUsage-capped leads.
crm:supportSalesSupport tickets (usage-capped).
crm:lead-scoringSales
crm:deal-stagesSales
crm:quotesSales
crm:csv-importSales
crm:custom-fieldsSalesUsage-capped.
crm:contact-filtersSales
crm:lead-filtersSales
crm:data-cleanupSalesData Health (dedup, bulk-fix).
crm:exportSalesWhole-workspace Data Export (admin-only).
crm:email-integrationSales
crm:ai-lead-enrichmentStudio + AI PackPLANNED (reserved AI key).
crm:ai-deal-summarizerStudio + AI PackPLANNED (reserved AI key).
crm:ai-next-best-actionStudio + AI PackPLANNED (reserved AI key).

workspace_data_export is an org flag (not in defaultFeatures) stored in feature_flags; it gates crm:export (CRM → Data Export) together with the crm module. It appears in Workspace admin → Feature flags.

Projects

KeyMin plateNotes
projects:crudFreeUsage-capped (3 projects on Free).
projects:tasksFreeUsage-capped tasks.
projects:kanbanGrowth
projects:calendarGrowth
projects:commentsGrowth
projects:time-tracking-viewGrowth
projects:subtasksGrowth
projects:milestonesGrowth
projects:ganttGrowth
projects:dependenciesGrowth
projects:time-tracking-logGrowth
projects:estimationGrowth
projects:budgetGrowth
projects:team-managementGrowth
projects:custom-fieldsGrowthUsage-capped.
projects:ai-task-generatorStudio + AI PackPLANNED (reserved AI key).
projects:ai-standup-summaryStudio + AI PackPLANNED (reserved AI key).

Platform

KeyMin plateNotes
platform:onboardingFree
platform:feature-flagsFreeThis admin surface.
platform:rbac-basicFree
platform:rbac-viewerFreeRead-only viewer role.
platform:notifications-emailStudioMetered against the email cap.
platform:lead-capture-apiSalesPublic lead-capture endpoint.
platform:api-accessFull Loop
platform:webhooksFull LoopOutbound webhooks.
platform:audit-logsFull Loop
platform:custom-brandingAgencyAlways on for white-label distribution.
platform:rbac-custom-rolesAgencyPLANNED (reserved).
platform:audit-logs-exportAgencyPLANNED (reserved).
platform:sso-samlAgencyPLANNED (reserved).
platform:scim-provisioningAgencyPLANNED (reserved).

Reserved keys (other modules)

KeyMin plateNotes
dashboard:ai-copilotStudio + AI PackPLANNED (reserved AI key).
automation:ai-triageStudio + AI PackPLANNED (reserved AI key).
forms:ai-form-builderStudio + AI PackPLANNED (reserved AI key).
reports:ai-narrativeStudio + AI PackPLANNED (reserved AI key).
reports:scheduledFull LoopPLANNED (reserved; not in default seed).

Feature key naming convention

type FeatureKey =
  | `crm:${string}`
  | `projects:${string}`
  | `forms:${string}`
  | `dashboard:${string}`
  | `analytics:${string}`
  | `reports:${string}`
  | `automation:${string}`
  | `invoicing:${string}`
  | `platform:${string}`
  | `ap:${string}`
  | `contracts:${string}`
  | `resource-planning:${string}`

Defined in packages/config/src/types.ts. All keys are used in packages/config/src/defaults.ts (defaultFeatures) and seeded into feature_flags on org creation.

Feature flags — OpsDash Docs | OpsDash