Apragya AI's first design principle is configuration over code. Every dropdown, label, status, type, stage in the platform is tenant-configurable, NOT hardcoded. When you're tempted to write a code branch for a customer-specific behaviour, ask: could this be a TenantAppConfig setting instead? Almost always, the answer is yes.
What TenantAppConfig is
Per-tenant configuration storage on every Enterprise App. Keyed by (tenant_id, app_slug, config_key), stored as JSONB. Loaded once on first request, cached with a 60-second TTL. Reads cost nothing; writes invalidate the cache for that tenant + app combo.
What goes there
- Dropdowns - stage names, status labels, category lists, priority levels
- Custom field schemas - the JSON-Schema fragment that extends a record's shape per tenant
- Taxonomies - the platform's category trees, customisable per tenant
- Per-app routing rules, escalation chains, approval matrices
- Per-app feature toggles inside the plan-feature-gate envelope
- Tenant-specific labels and translations on UI strings
What does NOT go there
- Business logic - if your customisation needs IF/ELSE branches, it's code, not config. Build it as a pipeline node or an agent prompt instead
- Secrets - API keys, credentials, tokens belong in the encrypted credentials store, not tenant config
- Large blobs - configs over 100KB get unwieldy in the JSONB. Move large content to object storage with a config row pointing to it
- Cross-tenant data - if it would apply to all tenants the same way, it's platform config, not tenant config
The hardcoded-enum trap
The single most common platform anti-pattern: hardcoding a status / stage / type list in code, then realising six months later you need to add a value for a specific tenant. The fix is invasive - touch every reference, write a migration, update every dropdown, update every agent prompt that references the enum. Avoid by starting with TenantAppConfig from day one, even if today's value list is just 3 items.
A real example
The CRM Pipeline ships with six stages: Discovery, Qualification, Proposal, Negotiation, Closed-Won, Closed-Lost. Hardcoded list, right? No. It's a TenantAppConfig key (crm.pipeline.stages) loaded fresh from JSONB per tenant. Acme Corp renames "Discovery" to "Cold" and adds a "Demo" stage between Qualification and Proposal. Zero code change. Acme's tenant sees the customised list everywhere - their dropdowns, their Kanban board, their agent prompts, their reports. Three other tenants on the same code path see the platform defaults.