Feature gating

Feature gating allows you to control access to specific functionality in your app based on a merchant's subscription plan. This approach enables you to create tiered offerings, encourage upgrades, and ensure merchants can only access the features they've paid for.

What is feature gating?

Feature gating is the practice of conditionally enabling or restricting access to certain features or capabilities based on predetermined criteria. In Mantle, these criteria are typically tied to:

  • The merchant's current subscription plan
  • Usage-based limits associated with features
  • Custom entitlements configured for specific merchants

Implementation strategies

There are several ways to implement feature gating in your app:

1. Frontend conditional rendering

Control what UI elements and features are displayed to merchants:

import { useMantle } from '@heymantle/react';

function AdvancedReporting() {
  const { isFeatureEnabled } = useMantle();
  
  const hasAdvancedReporting = isFeatureEnabled({
    featureKey: 'advanced_reporting'
  });
  
  return (
    <div>
      <h2>Reports</h2>
      
      {/* Basic reports shown to everyone */}
      <BasicReports />
      
      {/* Advanced reports only shown if feature is enabled */}
      {hasAdvancedReporting ? (
        <AdvancedReports />
      ) : (
        <UpgradePrompt feature="advanced_reporting" />
      )}
    </div>
  );
}

2. Backend route protection

Secure API endpoints or server-side functionality:

app.post('/api/advanced-reports', async (req, res) => {
  try {
    // Check if merchant has access to advanced reporting
    const hasFeature = await mantleClient.isFeatureEnabled({
      featureKey: 'advanced_reporting',
      customerApiToken: req.headers['x-customer-api-token']
    });
    
    if (!hasFeature) {
      return res.status(403).json({
        error: 'Feature not available on your current plan'
      });
    }
    
    // Feature is available, proceed with generating report
    const report = await generateAdvancedReport(req.body);
    return res.json({ report });
    
  } catch (error) {
    return res.status(500).json({ error: error.message });
  }
});

3. Middleware approach

Create reusable middleware for feature checks:

function requireFeature(featureKey) {
  return async (req, res, next) => {
    try {
      const hasFeature = await mantleClient.isFeatureEnabled({
        featureKey,
        customerApiToken: req.headers['x-customer-api-token']
      });
      
      if (!hasFeature) {
        return res.status(403).json({
          error: `Feature '${featureKey}' not available on your current plan`
        });
      }
      
      next();
    } catch (error) {
      return res.status(500).json({ error: error.message });
    }
  };
}

// Use middleware to protect routes
app.post('/api/advanced-reports', requireFeature('advanced_reporting'), (req, res) => {
  // Feature check already passed, generate report
  const report = generateAdvancedReport(req.body);
  return res.json({ report });
});

4. Limit-based gating

For features with numerical limits:

async function createProduct(productData) {
  const limit = await mantleClient.limitForFeature({
    featureKey: 'product_limit'
  });
  
  const customer = await mantleClient.getCustomer();
  const used = customer.usage['Product limit']?.currentValue || 0;
  
  if (used >= limit) {
    throw new Error('Product limit reached. Please upgrade your plan for more products.');
  }
  
  // Within limits, proceed with product creation
  const product = await actuallyCreateProduct(productData);
  
  // Track usage
  await mantleClient.sendUsageEvent({
    eventName: 'product_created',
    properties: { productId: product.id }
  });
  
  return product;
}

Upgrade prompts

When a merchant attempts to access a gated feature, provide clear upgrade paths:

function UpgradePrompt({ feature }) {
  const { plans } = useMantle();
  
  // Find plans that include this feature
  const eligiblePlans = plans.filter(plan => 
    plan.features[feature]?.value === true
  );
  
  return (
    <Card>
      <Text variant="heading">Unlock Advanced Features</Text>
      <Text>This feature requires an upgrade to access.</Text>
      
      <Stack>
        {eligiblePlans.map(plan => (
          <PlanCard 
            key={plan.id}
            plan={plan}
            primaryAction={{
              content: `Upgrade to ${plan.name}`,
              onAction: () => handleUpgrade(plan.id)
            }}
          />
        ))}
      </Stack>
    </Card>
  );
}

Best practices

  1. Check early, check often - Validate feature access early in your request lifecycle
  2. Provide clear feedback - Explain why features are unavailable and how to access them
  3. Cache check results - Store feature availability results to reduce API calls when appropriate
  4. Handle edge cases - Account for error scenarios and network issues
  5. Be consistent - Apply the same gating patterns across your application
  6. Keep UI in sync - Ensure interfaces don't show options for features merchants can't access

Did this page help you?