Reference

Feature flags

Feature access in Quattora 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
featureLock()lib/auth/ensure-page-access.tsLayouts and pages: returns the lock screen to draw in place if no access
tieredFeatureLock()lib/auth/ensure-page-access.tsLayouts and pages: 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

Plans are strictly nested — every plan includes everything below it. The public self-serve columns are Free, Studio, Team, Firm; Dedicated is sold in a conversation; legacy rungs are hidden and honoured as their successor. Yearly prices, per month, from price book v1:

  • Free — $0/mo yearly, $0/mo monthly
  • Studio — $29/mo yearly, $35/mo monthly
  • Team — $99/mo yearly, $119/mo monthly
  • Firm — $249/mo yearly, $299/mo monthly
  • Dedicated — $449/mo yearly, $499/mo monthly

Add-on packs extend a plan without changing it: AI Pack ($29/mo, from Free); e-Invoicing / Peppol ($29/mo, from Studio). Keys marked PLANNED below are reserved (wired for gating) but not yet shipped.

Sub-module toggles (Settings → Modules)

feature_flags holds one row per module (crm, invoicing, projects, …) plus, since 2026-09-21, four exact-key rows a workspace can switch inside a module that stays on. Every reader resolves a key the same way (resolveFlagLookupKeys): the exact toggle row if the key is a toggle or one it covers, else the module row, else on. A module that is off wins over a toggle that is on; a toggle with no row inherits its module. The rows are written by the door and by Settings → Modules (applyModuleSelection, audit feature.toggle with a source).

Toggle rowModuleAlso switches off
crm:leadscrmcrm:lead-scoring, crm:lead-filters, crm:ai-lead-enrichment
crm:dealscrmcrm:deal-stages, crm:multiple-pipelines, crm:quotes, crm:ai-deal-summarizer, crm:ai-next-best-action
crm:sequencescrm—
invoicing:pageinvoicinginvoicing:invoices, invoicing:accounting-export, invoicing:accounting-sync, invoicing:recurring, invoicing:dunning, invoicing:portal, invoicing:rate-by-person, invoicing:einvoice-facturx, invoicing:einvoice-peppol, invoicing:ai-autodraft-grouping, invoicing:ai-polish, invoicing:ai-tax-category, invoicing:ai-reconcile, invoicing:ai-translate-lines, invoicing:ai-dunning-prediction

All feature keys

CRM

KeyFrom planNotes
crm:contactsFreeUsage-capped by plan.
crm:companiesFreeUsage-capped by plan.
crm:activitiesFreeActivity log.
crm:contact-fieldsFreeStandard contact fields.
crm:gdpr-exportFreePer-contact data export.
crm:dealsStudioUsage-capped pipeline deals.
crm:leadsStudioUsage-capped leads.
crm:supportStudioSupport tickets (usage-capped).
crm:lead-scoringStudio
crm:deal-stagesStudio
crm:quotesStudio
crm:csv-importFree
crm:custom-fieldsStudioUsage-capped.
crm:contact-filtersStudio
crm:lead-filtersStudio
crm:data-cleanupStudioData Health (dedup, bulk-fix).
crm:exportFreeWhole-workspace Data Export (admin-only).
crm:email-integrationStudio
crm:ai-lead-enrichmentFreeAI action — counts against the monthly allowance.
crm:ai-deal-summarizerFreeAI action — counts against the monthly allowance.
crm:ai-next-best-actionFreeAI action — counts against the monthly allowance.

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

KeyFrom planNotes
projects:crudFreeUsage-capped by plan.
projects:tasksFreeUsage-capped tasks.
projects:kanbanFree
projects:calendarFree
projects:commentsTeam
projects:time-tracking-viewTeam
projects:subtasksTeam
projects:milestonesTeam
projects:ganttTeam
projects:dependenciesTeam
projects:time-tracking-logTeam
projects:estimationTeam
projects:budgetTeam
projects:team-managementTeam
projects:custom-fieldsTeamUsage-capped.
projects:ai-task-generatorFreeAI action — counts against the monthly allowance.
projects:ai-standup-summaryFreeAI action — counts against the monthly allowance.

Platform

KeyFrom planNotes
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-apiStudioPublic lead-capture endpoint.
platform:api-accessTeam
platform:webhooksTeamOutbound webhooks.
platform:audit-logsTeam
platform:custom-brandingFirmAlways on for white-label distribution.
platform:rbac-custom-rolesFirmCustom roles = a base role plus overrides (00417).
platform:audit-logs-exportFirmExport CSV on the audit-log page.
platform:sso-samlFirmSAML single sign-on via Supabase Auth (Pro).
platform:scim-provisioningFirmSCIM 2.0 provisioning endpoint.

AI actions and reserved keys (other modules)

KeyFrom planNotes
dashboard:ai-copilotFreeAI action — counts against the monthly allowance.
automation:ai-triageFreeAI action — counts against the monthly allowance.
forms:ai-form-builderFreeAI action — counts against the monthly allowance.
reports:ai-narrativeFreeAI action — counts against the monthly allowance.
reports:scheduledTeamPLANNED (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.