The Problem with À La Carte Entitlements
For many Software-as-a-Service (SaaS) companies, the allure of offering individual features à la carte to customers can be strong. It promises maximum flexibility, allowing users to cherry-pick exactly what they need and pay only for that. However, as one SaaS provider discovered, this model, while flexible on paper, often devolves into a practical nightmare. The core issue lies in maintaining entitlement logic. When a system has to track subscriptions to individual features alongside implicit plans, it creates two sources of truth. Every feature gate within the application must then query both sources, leading to increased complexity, potential for desynchronization, and a significant maintenance burden.
This complexity becomes particularly acute as the user base and feature set grow. What starts as a seemingly elegant solution for customer choice can quickly become a tangled web for developers and support teams. Debugging entitlement issues becomes a chore, and introducing new features requires careful consideration of how they fit into the existing à la carte structure, potentially exacerbating the problem. The temptation to simply rip out the old system and replace it with a new one—a 'big bang' migration—is often present, but this approach carries substantial risks. Any in-flight subscription changes, active feature checks, or ongoing user sessions could be disrupted mid-transaction, leading to a catastrophic failure and a severe loss of customer trust.
The Solution: A Phased Migration to Plans
Recognizing the unsustainable complexity of their à la carte feature subscription model, the team embarked on a deliberate migration to a simpler, plans-only structure. The fundamental shift was from paying for discrete features to paying for tiered plans, with each plan bundling a predefined set of features. This approach consolidates the source of truth for entitlements, drastically reducing the complexity of the entitlement logic and the potential for errors. Instead of checking dozens of individual feature subscriptions, the system now only needs to determine which plan a customer is on.
The migration was executed in four distinct phases, a strategy designed to minimize risk and ensure continuity for existing customers. This phased approach is crucial for complex model changes, allowing the team to test and validate each step before proceeding. The principle followed was 'expand → migrate → contract': introduce the new model, move existing customers over, and then retire the old system.
Phase 1: Seed Plans
The first step involved defining and seeding the new plan structure within the system. This meant creating the actual plan definitions, including which features would be associated with each tier. This phase focused on establishing the new foundation without impacting existing customer subscriptions or feature access. It was about building the target state in parallel with the existing system.
Phase 2: Gate on Plans
Once the new plans were established, the next phase was to implement the feature gates to check against the new plan-based entitlements. Crucially, this was done *in addition to* the existing à la carte checks. This 'double-gating' ensures that even if the old system had a discrepancy, the new plan-based logic would still correctly enforce feature access. It acts as a safety net, allowing the team to gain confidence in the new gating mechanism before relying on it exclusively. During this phase, the system would effectively respect both the old à la carte subscriptions and the new plan entitlements, prioritizing the new model where conflicts might arise, but not yet removing the old system.

Phase 3: Migrate Organizations
With the new plan structure defined and the gating logic validated, the team began migrating existing organizations from their à la carte subscriptions to the new plans. This is the most critical transition phase. Each organization's current feature subscriptions were analyzed and mapped to the most appropriate new plan. Customers were likely informed about this change, and the migration process would have involved updating their subscription records to reflect the new plan, while simultaneously disabling their previous à la carte feature subscriptions. This phase required careful data handling to ensure no features were lost or erroneously gained during the transition. The 'expand → migrate' principle is most evident here: the new system (plans) was expanded to cover all existing functionality, and then customers were migrated into it.
Phase 4: Delete Old Machinery
The final phase involved retiring the old à la carte subscription system entirely. This included removing the old database columns, the legacy entitlement checks, and any related background jobs or administrative interfaces. This 'contract' phase is essential for realizing the full benefits of the migration. By fully removing the old system, the team eliminates the dual source of truth, simplifies the codebase, and reduces the ongoing maintenance overhead. This step should only be taken after thorough validation that all customers have been successfully migrated and that the new plan-based system is stable and reliable.
Lessons Learned: The Expand-Migrate-Contract Mantra
The most significant lesson from this migration is the power of the 'expand → migrate → contract' strategy for complex system changes, particularly those involving core business logic like billing and entitlements. Attempting a 'big bang' rewrite is fraught with peril. By expanding the new system to run in parallel, migrating data and users incrementally, and only then contracting the old system, the risk of disruption is dramatically reduced. This approach allows for continuous operation, gradual validation, and a much smoother transition for both the engineering team and the customer base. It transforms a potentially catastrophic event into a manageable, phased rollout. If you run a SaaS business with complex entitlement logic, consider this methodical approach before undertaking any major billing or feature access model changes.
