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:
- Plate check: Is the org's plate at or above the feature's
minPlate(plus any required add-on pack)? - Subscription status: Must be
activeortrialing - Feature flag:
feature_flags.enabled— is it enabled for this org? - 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
| Function | Location | Purpose |
|---|---|---|
| canAccessFeature() | org-context.tsx | Client: sidebar, command palette, route guards |
| requireFeatureAccess() | admin.ts → admin-feature-gates.ts | Server actions: returns error if no access |
| featureLock() | lib/auth/ensure-page-access.ts | Layouts and pages: returns the lock screen to draw in place if no access |
| tieredFeatureLock() | lib/auth/ensure-page-access.ts | Layouts and pages: plate-gated sub-features (e.g. crm:export layout) |
| checkFeature() | admin.ts → admin-feature-gates.ts | Subscription/entitlement check only — no role check |
| checkUsageLimit() | helpers.ts | Usage-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 row | Module | Also switches off |
|---|---|---|
| crm:leads | crm | crm:lead-scoring, crm:lead-filters, crm:ai-lead-enrichment |
| crm:deals | crm | crm:deal-stages, crm:multiple-pipelines, crm:quotes, crm:ai-deal-summarizer, crm:ai-next-best-action |
| crm:sequences | crm | — |
| invoicing:page | invoicing | invoicing: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
| Key | From plan | Notes |
|---|---|---|
| crm:contacts | Free | Usage-capped by plan. |
| crm:companies | Free | Usage-capped by plan. |
| crm:activities | Free | Activity log. |
| crm:contact-fields | Free | Standard contact fields. |
| crm:gdpr-export | Free | Per-contact data export. |
| crm:deals | Studio | Usage-capped pipeline deals. |
| crm:leads | Studio | Usage-capped leads. |
| crm:support | Studio | Support tickets (usage-capped). |
| crm:lead-scoring | Studio | |
| crm:deal-stages | Studio | |
| crm:quotes | Studio | |
| crm:csv-import | Free | |
| crm:custom-fields | Studio | Usage-capped. |
| crm:contact-filters | Studio | |
| crm:lead-filters | Studio | |
| crm:data-cleanup | Studio | Data Health (dedup, bulk-fix). |
| crm:export | Free | Whole-workspace Data Export (admin-only). |
| crm:email-integration | Studio | |
| crm:ai-lead-enrichment | Free | AI action — counts against the monthly allowance. |
| crm:ai-deal-summarizer | Free | AI action — counts against the monthly allowance. |
| crm:ai-next-best-action | Free | AI 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
| Key | From plan | Notes |
|---|---|---|
| projects:crud | Free | Usage-capped by plan. |
| projects:tasks | Free | Usage-capped tasks. |
| projects:kanban | Free | |
| projects:calendar | Free | |
| projects:comments | Team | |
| projects:time-tracking-view | Team | |
| projects:subtasks | Team | |
| projects:milestones | Team | |
| projects:gantt | Team | |
| projects:dependencies | Team | |
| projects:time-tracking-log | Team | |
| projects:estimation | Team | |
| projects:budget | Team | |
| projects:team-management | Team | |
| projects:custom-fields | Team | Usage-capped. |
| projects:ai-task-generator | Free | AI action — counts against the monthly allowance. |
| projects:ai-standup-summary | Free | AI action — counts against the monthly allowance. |
Platform
| Key | From plan | Notes |
|---|---|---|
| platform:onboarding | Free | |
| platform:feature-flags | Free | This admin surface. |
| platform:rbac-basic | Free | |
| platform:rbac-viewer | Free | Read-only viewer role. |
| platform:notifications-email | Studio | Metered against the email cap. |
| platform:lead-capture-api | Studio | Public lead-capture endpoint. |
| platform:api-access | Team | |
| platform:webhooks | Team | Outbound webhooks. |
| platform:audit-logs | Team | |
| platform:custom-branding | Firm | Always on for white-label distribution. |
| platform:rbac-custom-roles | Firm | Custom roles = a base role plus overrides (00417). |
| platform:audit-logs-export | Firm | Export CSV on the audit-log page. |
| platform:sso-saml | Firm | SAML single sign-on via Supabase Auth (Pro). |
| platform:scim-provisioning | Firm | SCIM 2.0 provisioning endpoint. |
AI actions and reserved keys (other modules)
| Key | From plan | Notes |
|---|---|---|
| dashboard:ai-copilot | Free | AI action — counts against the monthly allowance. |
| automation:ai-triage | Free | AI action — counts against the monthly allowance. |
| forms:ai-form-builder | Free | AI action — counts against the monthly allowance. |
| reports:ai-narrative | Free | AI action — counts against the monthly allowance. |
| reports:scheduled | Team | PLANNED (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.