Feature flags determine what users can do. License keys determine who can do it. When you combine them, you get entitlement-based feature gating — the foundation of modern SaaS monetization. Instead of "is this feature on or off?" you ask "does this customer's license include this feature?"
Why License-Driven Feature Flags?
- Revenue segmentation — Different plans unlock different features
- Self-service upgrades — Users see locked features and can upgrade instantly
- Graceful downgrades — When a subscription ends, features are soft-locked, not deleted
- Trial management — Unlock all features during trial, then lock to their paid tier
- Enterprise customization — Per-customer feature sets without code deploys
Architecture: License as the Source of Truth
Your license validation response should include a features array that your application uses to gate UI and functionality:
// License validation response
{
"valid": true,
"plan": "professional",
"features": [
"analytics",
"custom-domains",
"api-access",
"team-management",
"priority-support",
"webhooks"
],
"limits": {
"domains": 10,
"apiCalls": 100000,
"teamMembers": 25
}
}
Client-Side Feature Gating
// Check features after validation
const license = await validateLicense(key, domain)
// UI gating
if (license.features.includes('analytics')) {
showAnalyticsDashboard()
} else {
showUpgradePrompt('analytics', license.plan)
}
// API gating
if (!license.features.includes('webhooks')) {
return Response.json({
error: 'feature_not_available',
message: 'Webhooks are available on Professional plans and above',
upgrade: '/pricing'
}, { status: 403 })
}
Feature Tiers: Designing Your Matrix
Map each feature to the minimum plan tier required. This matrix drives your pricing page, your validation logic, and your upgrade prompts.
| Feature | Builder | Starter | Professional | Business | Enterprise |
|---|---|---|---|---|---|
| Basic License Validation | ✓ | ✓ | ✓ | ✓ | ✓ |
| Domain Binding | 1 | 3 | 10 | 50 | Unlimited |
| Analytics Dashboard | — | ✓ | ✓ | ✓ | ✓ |
| API Access | — | ✓ | ✓ | ✓ | ✓ |
| Team Management | — | — | ✓ | ✓ | ✓ |
| Webhooks | — | — | ✓ | ✓ | ✓ |
| Custom Branding | — | — | — | ✓ | ✓ |
| SSO / SAML | — | — | — | — | ✓ |
| SLA Guarantee | — | — | — | — | ✓ |
| Dedicated Support | — | — | — | — | ✓ |
Upgrade Prompts: Converting Users
When a user encounters a locked feature, the experience should guide them toward upgrading — not frustrate them. The best upgrade prompts are:
- Contextual — Show the prompt where the feature would appear, not in a separate page
- Specific — "Unlock analytics with Starter ($29/mo)" beats "Upgrade your plan"
- Visual — Show a preview or screenshot of the locked feature
- One-click — Minimum friction from "I want this" to "I have this"
- Reversible — "Try free for 14 days" removes risk from the upgrade decision
// Contextual upgrade prompt
const showUpgradePrompt = (feature, currentPlan) => {
const upgradePlan = getMinimumPlan(feature)
const price = getPlanPrice(upgradePlan)
return `
<div class="feature-locked">
<div class="feature-preview">
<img src="/previews/${feature}.webp" alt="${feature} preview" />
<div class="feature-overlay">
<h3>${getFeatureTitle(feature)}</h3>
<p>Available on ${upgradePlan} and above</p>
<a href="/pricing" class="upgrade-btn">
Upgrade to ${upgradePlan} — ${price}/mo
</a>
</div>
</div>
</div>
`
}
Server-Side Enforcement
Client-side feature gating is for UX. Server-side enforcement is for security. Always verify feature entitlements on the server before processing requests.
// Middleware: enforce feature entitlement
const requireFeature = (feature) => async (req, res, next) => {
const license = await validateLicense(req.headers.authorization)
if (!license.valid) {
return res.status(401).json({ error: 'invalid_license' })
}
if (!license.features.includes(feature)) {
return res.status(403).json({
error: 'feature_not_available',
feature,
requiredPlan: getMinimumPlan(feature),
currentPlan: license.plan,
upgrade: '/pricing'
})
}
req.license = license
next()
}
// Usage
app.get('/api/analytics', requireFeature('analytics'), analyticsHandler)
app.post('/api/webhooks', requireFeature('webhooks'), webhookHandler)
Graceful Downgrades
When a customer downgrades or lets their subscription lapse, don't delete their data. Instead:
- Soft-lock features — Show the data but disable editing
- Grace period — Give 14 days before locking (payment retries happen)
- Data preservation — Keep analytics, configurations, and history intact
- Easy re-upgrade — One click to restore access without re-configuration
A/B Testing Premium Features
Use feature flags to test whether a feature should be included in a lower tier. Give a random subset of Starter users access to a Professional feature for 30 days, then measure:
- Does usage of the feature increase overall engagement?
- Does it drive more upgrades to Professional?
- Does including it in Starter reduce Professional conversions?
Implementation Checklist
- Define your feature matrix — which features belong to which plans
- Include features in your license validation response
- Implement client-side UI gating with contextual upgrade prompts
- Add server-side middleware that enforces feature entitlements
- Build graceful downgrade flows with data preservation
- Instrument feature usage for analytics
- Test upgrade flows end-to-end before launch
Ship licensing in your next release
5 licenses, 500 validations/month, full API access. Set up in under 5 minutes — no credit card required.