Overview
Entitlements control which features customers can access based on their active subscriptions and purchased products. The entitlement system provides a flexible way to restrict functionality and ensure customers only access features they’ve paid for.
How Entitlements Work
Frontier’s entitlement system follows a simple hierarchy:
Features Belong to Products
Each feature is associated with one or more products. When a product is purchased, customers get access to all its features.
Products Belong to Plans
Products are bundled into plans. When a customer subscribes to a plan, they get access to all products in that plan.
Access is Based on Active Subscriptions
Only active subscriptions grant entitlements. Trialing, canceled, or expired subscriptions are considered when determining state.
Checking Entitlements
Verify if a customer has access to a specific feature:
Basic Entitlement Check
cURL
Response (Has Access)
Response (No Access)
curl -X POST 'https://frontier.example.com/v1beta1/organizations/{org_id}/billing/{billing_id}/entitlements/{feature_id}/check' \
-H 'Authorization: Bearer <token>'
Entitlement Check Logic
The entitlement check performs these steps:
Get Active Subscriptions
Retrieve all subscriptions for the customer and filter to only active ones.
Get Feature Details
Look up the feature being checked and identify which products include it.
Match Products to Plans
Check if any of the feature’s products belong to plans the customer is subscribed to.
Return Result
Return true if a match is found, false otherwise.
Implementation Examples
Middleware for Feature Gates
Protect API endpoints based on feature entitlements:
// Express.js middleware example
const checkEntitlement = ( featureId ) => {
return async ( req , res , next ) => {
const { orgId , billingId } = req . params ;
try {
const response = await fetch (
`https://frontier.example.com/v1beta1/organizations/ ${ orgId } /billing/ ${ billingId } /entitlements/ ${ featureId } /check` ,
{
headers: {
'Authorization' : req . headers . authorization
}
}
);
const { entitled } = await response . json ();
if ( ! entitled ) {
return res . status ( 403 ). json ({
error: 'Feature not available in your current plan' ,
upgrade_url: '/billing/plans'
});
}
next ();
} catch ( error ) {
res . status ( 500 ). json ({ error: 'Failed to check entitlement' });
}
};
};
// Protect routes
app . post ( '/api/advanced-analytics' ,
checkEntitlement ( 'advanced_analytics' ),
( req , res ) => {
// Only accessible if customer has advanced_analytics feature
res . json ({ data: runAdvancedAnalytics () });
}
);
Frontend Feature Flags
Show or hide UI elements based on entitlements:
import { useEffect , useState } from 'react' ;
function useEntitlement ( featureId : string ) {
const [ entitled , setEntitled ] = useState ( false );
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
const checkAccess = async () => {
try {
const response = await fetch (
`/v1beta1/organizations/ ${ orgId } /billing/ ${ billingId } /entitlements/ ${ featureId } /check` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { entitled } = await response . json ();
setEntitled ( entitled );
} catch ( error ) {
console . error ( 'Entitlement check failed:' , error );
setEntitled ( false );
} finally {
setLoading ( false );
}
};
checkAccess ();
}, [ featureId ]);
return { entitled , loading };
}
// Use in component
function AdvancedFeatures () {
const { entitled , loading } = useEntitlement ( 'advanced_analytics' );
if ( loading ) {
return < Spinner />;
}
if ( ! entitled ) {
return (
< UpgradePrompt >
Upgrade to Pro to access advanced analytics
</ UpgradePrompt >
);
}
return < AdvancedAnalyticsDashboard />;
}
Server-Side Feature Gates (Go)
package middleware
import (
" context "
" net/http "
" github.com/yourapp/billing "
)
func RequireFeature ( featureID string , entitlementService * billing . EntitlementService ) func ( http . Handler ) http . Handler {
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
customerID := r . Context (). Value ( "customer_id" ).( string )
entitled , err := entitlementService . Check ( r . Context (), customerID , featureID )
if err != nil {
http . Error ( w , "Failed to check entitlement" , http . StatusInternalServerError )
return
}
if ! entitled {
http . Error ( w , "Feature not available in your plan" , http . StatusForbidden )
return
}
next . ServeHTTP ( w , r )
})
}
}
// Protect endpoints
mux . Handle ( "/api/advanced-analytics" ,
RequireFeature ( "advanced_analytics" , entitlementService )(
http . HandlerFunc ( advancedAnalyticsHandler ),
),
)
Plan Eligibility
Beyond basic feature checks, Frontier provides plan eligibility verification to ensure customers meet requirements for their subscriptions.
Check Plan Eligibility
The eligibility check verifies that:
Seat limits are not exceeded : For per-seat products
Organization size is within bounds : Based on member count
All subscription constraints are met : Custom plan requirements
err := entitlementService . CheckPlanEligibility ( ctx , customerID )
if err != nil {
// Customer is not eligible for their current plan
// This might happen if they exceed seat limits
return errors . Wrap ( err , "plan eligibility check failed" )
}
Seat Limit Enforcement
For products with per_seat behavior and configured seat_limit, eligibility checks prevent organizations from exceeding their user limit:
// When adding a new user
func AddUserToOrganization ( ctx context . Context , orgID , userID string ) error {
// First check if adding this user would breach seat limits
err := entitlementService . CheckPlanEligibility ( ctx , customerID )
if err != nil {
return errors . New ( "cannot add user: seat limit exceeded" )
}
// Proceed with adding user
return organizationService . AddMember ( ctx , orgID , userID )
}
Always check plan eligibility before adding users to organizations with per-seat subscriptions. Failing to do so may result in billing inconsistencies.
Active Subscription Criteria
A subscription is considered active for entitlement purposes if it meets all of these conditions:
State is active or trialing
Not canceled (canceled_at is null)
Not ended (ended_at is null or in the future)
Belongs to an active billing customer
{
"subscription" : {
"state" : "active" ,
"canceled_at" : null ,
"ended_at" : null ,
"trial_ends_at" : null ,
"current_period_end_at" : "2024-04-01T00:00:00Z"
}
}
Feature-Product-Plan Mapping
Defining the Hierarchy
features :
- name : api_access
title : API Access
- name : advanced_analytics
title : Advanced Analytics
- name : priority_support
title : Priority Support
products :
- name : basic_access
title : Basic Access
features :
- name : api_access
- name : pro_access
title : Pro Access
features :
- name : api_access
- name : advanced_analytics
- name : priority_support
plans :
- name : basic_plan
products :
- name : basic_access
- name : pro_plan
products :
- name : pro_access
Entitlement Results
Customer Plan api_access advanced_analytics priority_support basic_plan ✅ Yes ❌ No ❌ No pro_plan ✅ Yes ✅ Yes ✅ Yes None ❌ No ❌ No ❌ No
Caching Entitlements
For high-traffic applications, cache entitlement checks to reduce API calls:
const entitlementCache = new Map ();
const CACHE_TTL = 5 * 60 * 1000 ; // 5 minutes
async function checkEntitlement ( customerId , featureId ) {
const cacheKey = ` ${ customerId } : ${ featureId } ` ;
const cached = entitlementCache . get ( cacheKey );
if ( cached && Date . now () - cached . timestamp < CACHE_TTL ) {
return cached . entitled ;
}
const response = await fetch (
`/v1beta1/organizations/ ${ orgId } /billing/ ${ billingId } /entitlements/ ${ featureId } /check` ,
{ headers: { 'Authorization' : `Bearer ${ token } ` } }
);
const { entitled } = await response . json ();
entitlementCache . set ( cacheKey , {
entitled ,
timestamp: Date . now ()
});
return entitled ;
}
Invalidate the cache when subscriptions change to ensure customers immediately get access to new features or lose access when subscriptions are canceled.
Graceful Degradation
Provide a good user experience when entitlements fail:
interface FeatureGateProps {
featureId : string ;
fallback ?: React . ReactNode ;
upgradeUrl ?: string ;
children : React . ReactNode ;
}
function FeatureGate ({ featureId , fallback , upgradeUrl , children } : FeatureGateProps ) {
const { entitled , loading } = useEntitlement ( featureId );
if ( loading ) {
return < Skeleton />;
}
if ( ! entitled ) {
return fallback || (
< div className = "upgrade-prompt" >
< h3 > Upgrade Required </ h3 >
< p > This feature is not available in your current plan . </ p >
{upgradeUrl && (
<a href = { upgradeUrl } className = "btn btn-primary" >
View Plans
</a>
)}
</ div >
);
}
return <>{ children } </> ;
}
// Usage
< FeatureGate
featureId = "advanced_analytics"
upgradeUrl = "/billing/plans"
>
< AdvancedAnalyticsDashboard />
</ FeatureGate >
Best Practices
Check at Multiple Layers
Implement entitlement checks in both frontend (UI) and backend (API) for security and user experience.
Cache Wisely
Cache entitlement checks to reduce API calls, but ensure cache invalidation when subscriptions change.
Provide Clear Messaging
When access is denied, explain why and provide a path to upgrade.
Use Feature Flags
Combine entitlements with feature flags for gradual rollouts and A/B testing.
Monitor Denials
Track when users hit entitlement blocks to identify upgrade opportunities.
Test Edge Cases
Verify behavior during trial periods, at subscription renewal, and after cancellation.
Common Patterns
Tiered Feature Access
Offer progressive feature access across plans:
features :
- name : basic_api
title : Basic API (100 req/day)
- name : standard_api
title : Standard API (1000 req/day)
- name : unlimited_api
title : Unlimited API
products :
- name : starter
features : [ basic_api ]
- name : professional
features : [ standard_api ]
- name : enterprise
features : [ unlimited_api ]
Feature Bundles
Group related features into logical bundles:
features :
- name : collaboration_chat
- name : collaboration_comments
- name : collaboration_sharing
products :
- name : collaboration_bundle
title : Collaboration Suite
features :
- collaboration_chat
- collaboration_comments
- collaboration_sharing
Add-On Features
Offer optional features across all plans:
products :
# Add-ons available to all plans
- name : priority_support_addon
title : Priority Support Add-on
features :
- priority_support
- name : advanced_security_addon
title : Advanced Security
features :
- sso
- audit_logs
- custom_roles
Troubleshooting
Entitlement Check Returns False Despite Active Subscription
Verify:
Subscription state is active or trialing
Feature is correctly associated with a product
Product is included in the plan
Plan ID matches the subscription’s plan
Entitlement Granted After Subscription Canceled
Check if:
Subscription is still in current billing period
Cache hasn’t been invalidated
Background sync hasn’t updated subscription state
Seat Limit Not Enforced
Ensure:
Product has behavior: per_seat
seat_limit is configured in product config
Plan eligibility check is called before adding users
Next Steps
Products and Plans Define features and associate them with products
Subscriptions Understand how subscriptions grant entitlements
Credits Combine feature access with usage-based billing
Customers Manage customer accounts and subscriptions