Flagsmith Feature Flags Configuration
This guide explains how to configure and use feature flags in the ERP-Unlocked platform using Flagsmith.
Overview
Section titled βOverviewβERP-Unlocked uses Flagsmith for feature flag management. This enables:
- Gradual rollouts: Roll out features to a percentage of users
- Targeting: Enable features for specific organizations, users, or segments
- A/B testing: Test different feature variants
- Kill switches: Quickly disable features in production
Architecture
Section titled βArchitectureβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ Flagsmith Server ββ (Self-hosted or Flagsmith Cloud) ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββ΄ββββββββββββ βΌ βΌββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββ @repo/feature-flags (TS) β β packages/feature-flags-py ββ βββ server.ts (SSR/API) β β (Python Flagsmith client) ββ βββ client.ts (Browser) β ββββββββββββββββ¬βββββββββββββββββ βββ hooks.tsx (React) β βββββββββββββββββ¬ββββββββββββββββ βΌ βΌ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β Python Workers & Pipelines ββ Webapp / TS Temporal Worker β β (temporal-worker, dagster) βββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββEnvironment Variables
Section titled βEnvironment VariablesβRequired
Section titled βRequiredβ# Flagsmith environment key (server-side key)FLAGSMITH_ENVIRONMENT_KEY=ser.xxxxx
# Flagsmith API URL (self-hosted or cloud)FLAGSMITH_API_URL=https://flagsmith.yourdomain.com/api/v1Optional
Section titled βOptionalβ# Enable local evaluation (caches flags, reduces API calls)FLAGSMITH_LOCAL_EVALUATION=true
# Cache refresh interval in seconds (default: 60)FLAGSMITH_REFRESH_INTERVAL=60Using Feature Flags
Section titled βUsing Feature FlagsβIn Astro Pages (Server-Side)
Section titled βIn Astro Pages (Server-Side)β---import { getFeatureFlag } from '@repo/feature-flags';
// Check if feature is enabled for current userconst isNewDashboardEnabled = await getFeatureFlag('new_dashboard', { identifier: Astro.locals.user?.id, traits: { organizationId: Astro.locals.organization?.id, email: Astro.locals.user?.email, }});---
{isNewDashboardEnabled ? ( <NewDashboard />) : ( <LegacyDashboard />)}In API Routes
Section titled βIn API Routesβimport { getFeatureFlag } from '@repo/feature-flags';
export const GET: APIRoute = async ({ locals }) => { const useNewAlgorithm = await getFeatureFlag('new_matching_algorithm', { identifier: locals.user?.id, traits: { organizationId: locals.organization?.id, }, });
if (useNewAlgorithm) { // Use new implementation } else { // Use existing implementation }};In React Components
Section titled βIn React Componentsβimport { useFeatureFlag } from '@repo/feature-flags/hooks';
export function OrderEditor() { const showAdvancedOptions = useFeatureFlag('order_editor_advanced');
return ( <div> <BasicOptions /> {showAdvancedOptions && <AdvancedOptions />} </div> );}Using Feature Gates
Section titled βUsing Feature Gatesβimport { useFeatureGate } from '@repo/feature-flags/hooks';
export function FeaturePreview() { const { Gate } = useFeatureGate('beta_features');
return ( <Gate fallback={<UpgradeBanner />}> <BetaFeaturePanel /> </Gate> );}Current Feature Flags
Section titled βCurrent Feature Flagsβ| Flag Name | Description | Default |
|---|---|---|
email_enabled |
Enable email processing for orders | false |
dagster_enabled |
Enable Dagster-based data sync | false |
typesense_search |
Use Typesense for product search | false |
new_order_editor |
New React-based order editor | false |
matching_shipto_resolver_v1 |
Outer gate for the CK ship-to customer resolver (Temporal worker). The /api/erp/customers ship-to boost checks it too, and gates the customer-search APIβs client-echoed resolvedCustomerId shortcut (off = parameter ignored); review-v2 auto-validate auto-applies the resolver-marked customer (customer_selected match_type='auto_shipto'). See rollout doc below. |
false |
email_triage_trusted_sender_auth_required_v1 |
Temporal pull path only (Gmail/Graph β not webapp/Cloudflare email intake). Requires a dmarc=pass + provider marker in Authentication-Results before a trusted-sender domain match skips email triage (CWE-290 hardening; shadow-logs when off). No-op unless email_triage_trusted_senders_v1 is also on. See PR #2849 trusted-sender auth gap. |
false |
email_triage_internal_sender_v1 |
Temporal pull path only. Skips mail sent by the organizationβs own staff before content triage, so an org forwarding its own sales orders into its connected mailbox stops producing order rows. Matches the bare From address exactly (never by domain) against email_aliases rows with alias_type='user'. Evaluated per-connection, before the trusted-sender allowlist. Do not enable for any connection yet β the staff roster counts Ordermatic staff who join a customer org and never drops leavers; both blockers are tracked as P0 in TODOS.md. |
false |
email_triage_coupa_po_notices_v1 |
Temporal pull path only. Turns Coupa new-PO notices into orders. A *.coupahost.com sender with a βNew Purchase Order β¦ has been issuedβ subject routes to process as new_order (engine_status=coupa_po_override), and extraction sets the PO to VMSβs E4 form, COUP/ plus the last 7 digits, keeping the Coupa PO in the order notes. Invoice and payment notices from the same sender still skip. A sender that is not DMARC-aligned is logged, not blocked. Evaluated per-connection. |
false |
matching_coupa_customer_v1 |
Temporal pull path only. Binds a Coupa new-PO notice to the E4 account its PO prefix maps to in matchingConfig.customerAutobind.coupaAccounts (VMS: US to 5436, CA to 5492) instead of the name match, and binds an on-file ship-to only on a street or postal-code match. Falls through to the existing chain when the prefix is unmapped or the account is missing. Evaluated per-connection. |
false |
extraction_coupa_due_rules_v1 |
Due date and USDOT rules for Coupa new-PO notices, read from extraConfig.eclipse.coupaDueRules. The temporal-worker reads each finished-goods lineβs stock (SUM(MaterialOnHand.Quantity)) and desired level (desiredLevelColumn, VMS ReorderLevel) from E4 at extraction and sets the completion dates: in stock, next business day; short with a desired level, 5 business days; short with none, or stock unreadable, the order holds for review. A generic USDOT line (usdot.linePattern) holds the order for a USDOT number; submit refuses it until the CSR fills usdot.numberField in review, then binds the USDOT <number> item at the PO price. Evaluated per-connection. |
false |
email_triage_shopify_orders_v1 |
Temporal pull path only. Turns Shopify new-order notifications from stores listed in the ERP connectionβs extra_config.eclipse.shopify.stores into orders (engine_status=shopify_order_override), but only when DKIM or SPF passed for a Shopify domain. An unverified one keeps the modelβs decision, tagged shopify_sender_unverified. Replies and forwards never match. Evaluated per-connection. |
false |
extraction_shopify_orders_v1 |
Temporal pull path only. Reads a verified Shopify notification from a configured store with a deterministic parser instead of the model: SKU lines (with the storeβs skuMap for bundles), PO #<order>/<PO or unit>, shipping address, notes. Reason codes land on the order as sticky SHOPIFY_INTAKE review issues; no lines parsed means the model reads it. Evaluated per-connection. |
false |
email_triage_fleet_sold_order_v1 |
Temporal pull path only. Turns VMSβs Enterprise Fleet SOLD ORDER handoffs into orders. An email from rkraudel@vmsinc.com whose subject starts βSOLD ORDER: Enterprise Fleetβ routes to process as new_order (engine_status=fleet_sold_order_override), ahead of revision intake. RE:/FW: replies keep todayβs triage. Evaluated per-org. |
false |
extraction_fleet_sold_order_v1 |
Temporal pull path only. Reads those SOLD ORDER emails with a template parser (activities/fleet_sold_order.py) instead of the LLM: PO is the vehicleβs PO / Unit / VIN line for one vehicle, else See Notes; a stock rerun (Rerun: SOH) gets one finished-good line for the EFM program code; print reruns (Rerun: Yes), new programs and shell orders are held for review with no lines. When the ERP connection sets eclipse.fleetInstallRate, it also reads the programβs install rate from E4 (estimate, else newest job) into the notes; no rate holds the order for review. Parsed fields and E4 notes go to extracted_orders.intake_fields. Needs migration 0204. Evaluated per-org. |
false |
eclipse_e4_order_notes_v1 |
Per-connection. Eclipse E4 SubmitOrder writes ErpOrderInput.notes: headerDetail to header JobDetailDescription, componentDetails[i] to line iβs DetailDescription, shipmentInstructions to Shipment Instructions. Today only Fleet SOLD ORDERs carry notes. Off is byte-identical. |
false |
eclipse_e4_fleet_install_component_v1 |
Per-connection, unverified on E4. With eclipse_e4_order_notes_v1 on, appends an External Install component (Description Install, qty = vehicles, UD1 VMS 1) to Fleet jobs, priced at the install rate the worker read from E4 at extraction times the vehicles (unpriced with a submit warning when there is none), and leaves the header total to E4. Needs a TST- probe first. |
false |
eclipse_e4_historical_due_days_v1 |
Gates the historical-buffer fallback for the Eclipse E4 (VMS) ERP due date (resolveEclipseE4DueDateWithHistory, called from submit.ts and generate-quote.ts). When ON and thereβs no CSR-set completion date, dueDate is computed from a per-ship-to business-day buffer mined nightly into erp_ship_to_due_days by eclipse_e4_due_days_job, instead of reusing the POβs requested date unchanged. Requires eclipse_e4_due_dates_v1 ALSO ON for the connection β that flag gates whether the computed due date reaches the ERP at all; this flag alone changes nothing. OFF (default) = byte-identical to prior behavior. |
false |
inbox_conversation_view_v1 |
Org-scoped, webapp only. Turns on the inbox conversation view. One Conversation tab in the cockpit detail pane replaces the Email and Thread tabs and lists every message on the itemβs email thread with its triage outcome, archive and trash activity, and internal notes with @mentions that send an in-app notification. GET /api/work-items/:id/conversation and POST /api/work-items/:id/notes return 404 while it is off. Replying from the inbox is not built, and only inbound mail shows. |
false |
inbox_sla_engine_v1 |
Org-scoped. Turns on the order-turnaround SLA engine in the inbox: SLA deadlines (slaDueAt, slaWarnAt) are computed for new work items, the inbox page and /api/work-items/list sort open items before terminal ones and then by slaDueAt, and each row shows the SLA urgency pill, whose card (click, tap, Enter or Space) holds the deadline, schedule window and manual priority override. Off keeps chronological order, hides the pill even though every row carries slaTier, and makes /api/work-items/sla-policy, sla-policy-history and [id]/priority return 404. |
false |
agentic_shadow_submit_v1 |
Org-scoped. Records every intake agent submit (/api/orders/agent-submit, live or shadow) in agent_shadow_submissions: the payload the CSR submit path built, or the gate that held it. CSR submits, splits and Finished approvals in the org are recorded there too. It no longer forces the agent into dry run: live submit is agentic_auto_submit_enabled plus erp_write_enabled. Needs agentic_validation_enabled. Report: packages/eval/scripts/agent-shadow-compare.ts. |
false |
inbox_validation_state_v1 |
Org-scoped, webapp read path only (uxStateSql / deriveUxState in apps/webapp/src/lib/inbox). Derives an awaiting_review itemβs inbox category from the agentic validation verdict as well as its lines: unresolved lines, no ERP customer or a current verdict with blocking issues mean Needs fix; a verdict computed against older order content (auto_validation_version differs from order_content_version), a requires_review verdict or confidence below 0.95 mean Needs review. work_items.status is untouched. |
false |
Segments
Section titled βSegmentsβFlagsmith segments allow targeting users based on traits:
| Segment | Targeting Rule |
|---|---|
internal_team |
email CONTAINS @boonetek.com |
beta_users |
betaEnabled = true |
enterprise_orgs |
subscriptionTier = enterprise |
staging_environment |
environment = staging |
Python Services Integration
Section titled βPython Services IntegrationβPython services (apps/temporal-worker, apps/dagster) evaluate Flagsmith directly via packages/feature-flags-py (feature_flags_py), scoped to the connection (connection_<connectionId>) or Clerk organization identity (org_...):
from feature_flags_py import get_feature_flag
# In apps/temporal-worker (e.g. activities/typesense.py or workflows/email_processing.py):is_enabled = get_feature_flag( "matching_shipto_resolver_v1", identity=clerk_organization_id, traits={"clerk_organization_id": clerk_organization_id}, default=False,)if is_enabled: # Run flag-gated resolver path ...Adding New Flags
Section titled βAdding New Flagsβ-
Create the flag in Flagsmith UI
- Go to your Flagsmith project
- Create a new feature flag with a descriptive name (snake_case)
- Set default state (enabled/disabled)
- Add any targeting rules or segments
-
Use the flag in code
const isEnabled = await getFeatureFlag('your_new_flag', identity); -
Update this documentation
- Add the flag to the βCurrent Feature Flagsβ table above
Best Practices
Section titled βBest Practicesβ- Use descriptive names:
order_processing_v2notflag1 - Default to disabled: New features should be off by default
- Remove stale flags: After 100% rollout, remove flag checks from code
- Document flags: Keep this guide updated with current flags
- Use segments: Target internal team first, then beta users, then gradual rollout
Troubleshooting
Section titled βTroubleshootingβFlags not updating
Section titled βFlags not updatingβ- Check
FLAGSMITH_REFRESH_INTERVALsetting - Verify
FLAGSMITH_API_URLis correct - Check Flagsmith server health
Flag always returns default value
Section titled βFlag always returns default valueβ- Verify
FLAGSMITH_ENVIRONMENT_KEYis correct - Check flag exists in Flagsmith for the correct environment
- Verify identity traits match targeting rules
Performance issues
Section titled βPerformance issuesβ- Enable local evaluation:
FLAGSMITH_LOCAL_EVALUATION=true - Increase refresh interval for stable flags
- Use caching for high-traffic routes
Related Documentation
Section titled βRelated Documentationβ- Feature Flag Policy for ERP Writes - Mandatory policy, staged rollout protocol, and PR checklist
- Organization Settings Guide - Per-org configuration
- Deployment Guide - Environment setup