Product Entitlements with Feature Flags?
Your billing system knows user.plan == pro
, but your React code needs to know if it should let the user see the "advanced reporting" tab or calculate how many "active lists" they're allowed. How do we bridge this gap? How do we go from the SKU/plan to the actual product features that should be on or off. How can we handle product entitlements with feature flags?
The naive version of these systems is pretty easy to build. We could of course put if user.plan == :pro
checks everywhere, however we are going to feel pain from this almost immediately, because there are common entitlement curve-balls that will lead to spaghetti code and hard to understand systems once they are faced with reality. For today, let's see how to build a more resilient system by:
- Taking a real pricing page and modeling it.
- Throwing some common curve-balls at it.
Some organizations are going to want a whole separate entitlements system, but let's look at how to do this with the feature flag system you already know and love. We'll implement using Prefab, but these concepts apply to any feature flag tool.
Our Example
Let's implement a portion of HubSpot SalesHub pricing page as our guinea pig here. It has a mix of on/off features as well as limits.
The first thing we need to do is model how we think about a customer's plan. Our feature flag tool is not the system of record for what SKU / billing plan a user or team is on, so we should pass that in as context to the tool. But what should that context look like?
Design your pricing and tools so you can adapt them later is a great read that gives us a good place to start. I'm going to use the SKU definition from that as a start point for us, but it's worth a read in its entirety as well.
While it is helpful to have a single canonical SKU as in the image above, it will be easier to work with if we also split it up into the component parts. (I didn't use an integer for the product part because that's a bit more confusing for our example today with a product that isn't yours, but it's a fine idea).
Here's a good straw-person context we can start with:
{
user: {
key: "123",
name: "Bob Customer",
email: "bob@fexample.com"
},
team: {
key: "456",
email: "Example Corp"
},
plan: {
key: "us-starter-myproduct-01-v0",
country: "us",
tier: "starter",
product: myproduct
payment: 1,
version: 0,
}
}
Modelling Out The Smart Send Times Feature
Ok, we've got a feature calls "Smart send times"? Initially, it's a no-brainer: a boolean flag that flips on or off based on the user's plan.
Our first pass at modeling out smart send times is really easy. It's a boolean flag, and it varies by plan tier. We can simply model it like this:
In code, we'll just call
const context = //context from above
// Initialize client
const options = { apiKey: 'YOUR_CLIENT_API_KEY', context: new Context(context) };
await prefab.init(options);
prefab.isEnabled('features.smart-send-times');
This will work great for a while, but let's throw in our first curve-ball.
Demos & Instant Overrides
For this curve-ball lets figure out what to do when, inevitably, sales gets in touch and says that they need to be able to demo this feature to users in their account. The user is still going to be in the starter
plan, but we want to temporarily give them the same access (for this feature) as if they were in pro
The easiest way to do this is to make an one-off override for the customer that sales wants to demo to.
To do this, we can use the context search screen which lets us type in the name of the customer. From the resulting page we can see how every flag evaluates for them. We can then click and change to set the flag variant manually for them.
That creates a new rule for us, specifying that this particular user should have access to the feature. Rules are evaluated in order, so the top rule will win here.
Improving One-Offs with Shared Segments
What we did there works fine, but let's throw another curve-ball. It's pretty common to have a situation where a salesperson always wants to demo 10+ different features. We sure don't want them to have to edit 10 different flags and then remember to undo 10 different flags in order to make this happen.
A good way to make this better would be to create a segment called currently-demoing
, then we can reference this segment in the rules.
When a sales person needs to demo, they can quickly add the team or user key and instantly that team will be upgraded for all flags that use this segment.
We can use this segment as a top-priority rule in the features we want to demo.
When using segments, it can be a bit more challenging to know exactly why a user is getting a feature. To help you with that, you
can use the context view again, but this time hover over the ?
next to each flag. This will show you all of the rules for that flag and highlight which rule is matching. Here we can see that we are matching true
because of this currently-demoing
segment.
Segments are powerful and can help you bring order to your models. They will be a good aid in solving challenges around one-off contracts as well. Remember, just because you offer 3 tiers on the website, doesn't mean there isn't going to be a 4th, 5th and 6th tier once sales starts negotiating. Good naming is key to making this work so game out a number of scenarios before you commit to ensure that you have the best chance of success.
Modeling Pricing Plan Changes
The next entitlement we'll model will be the number of "Calling Minutes". Enterprise should have 12,000, Pro 3000 and Starter 50.0
We can use a FeatureFlag with integer values here.
Let's throw in a new curve-balls this time. These limits seem like something we'll want to experiment with. Let's say we want to try reducing the number of minutes on the pro plan, but we don't want to change anything for existing users. This is where our good SKU modeling is going to help.
We'll be changing the sku from:
us-pro-myproduct-01-v0 #before
us-pro-myproduct-01-v1 #after
We can no longer just use 'plan.tier' to determine the number of calling minutes, so let's use the plan.version
attribute to lock down the behavior for the Pro plan version 0.
We have our legacy pricing locked in and won't be changing existing customers. If we wanted, we could even start experimenting. To model a 50/50 split of calling minutes, we can do a percentage rollout. Note that we should be careful to select the sticky property
. If we want all members on a team to have a consistent experience, we should choose team.key
instead of user.key
.
Summary
Dealing with product entitlements and feature flags is part science, part art, and a whole lot of not shooting yourself in the foot. It’s about laying down a flexible foundation, so you’re not boxed in when you need to evolve. And remember, it’s always smarter to hash out your game plan with someone else before you commit. Two heads are better than one, especially when one is yours :)
Make sure to consider the curve-balls we considered here:
- How will you do demos?
- How will you support one-off contracts?
- How will keep legacy plans consistent while you iterate on new pricing?
If you’re neck-deep in figuring out how to model your features and entitlements, don’t go it alone. Ping me, and let’s nerd out over it. Because at the end of the day, we’re all just trying to make software that doesn’t suck.