Error handling

Effective error handling is crucial when implementing feature gating in your app. Proper error management ensures merchants understand why they can't access certain features and provides clear paths to resolve these limitations.

Common error scenarios

When implementing feature controls, you may want to anticipate and handle these types of error scenarios in your app:

📘

These examples are not explicit errors returned from Mantle's API. They are common cases you may encounter based on your own app's business logic or implementation. Your code should handle them appropriately based on your use case.

  1. Feature not available - The feature isn't included in the merchant's current plan
  2. Usage limit reached - The merchant has hit the maximum allowed usage for a feature
  3. API errors - Issues connecting to Mantle's API or retrieving feature status
  4. Invalid feature keys - Attempting to check features that don't exist

Implementing error handling

Try-catch pattern

Use try-catch blocks to handle feature-related errors:

try {
  await createProduct(data);
} catch (error) {
  if (error.message.includes('not available')) {
    // Feature not on plan - show upgrade prompt
    showUpgradeModal('products');
  } else if (error.message.includes('limit reached')) {
    // Usage limit hit - show limit reached message
    showLimitReachedMessage('products');
  } else {
    // Handle other errors
    handleError(error);
  }
}

Error classification

Create helper functions to classify and handle different error types:

function isFeatureNotAvailableError(error) {
  return error.message.includes('not available') || 
         error.message.includes('not enabled');
}

function isLimitReachedError(error) {
  return error.message.includes('limit reached') || 
         error.message.includes('exceeded');
}

function handleFeatureError(error, featureKey) {
  if (isFeatureNotAvailableError(error)) {
    return showUpgradeModal(featureKey);
  }
  
  if (isLimitReachedError(error)) {
    return showLimitReachedMessage(featureKey);
  }
  
  // Default error handling
  logError(error);
  showGenericErrorMessage();
}

Custom error types

Define custom error classes for better error handling:

class FeatureNotAvailableError extends Error {
  constructor(featureKey) {
    super(`Feature '${featureKey}' is not available on your current plan`);
    this.name = 'FeatureNotAvailableError';
    this.featureKey = featureKey;
  }
}

class FeatureLimitReachedError extends Error {
  constructor(featureKey, limit, used) {
    super(`Limit reached for feature '${featureKey}' (${used}/${limit})`);
    this.name = 'FeatureLimitReachedError';
    this.featureKey = featureKey;
    this.limit = limit;
    this.used = used;
  }
}

// Using custom errors
async function createProduct(productData) {
  const hasFeature = await mantleClient.isFeatureEnabled({
    featureKey: 'products'
  });
  
  if (!hasFeature) {
    throw new FeatureNotAvailableError('products');
  }
  
  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 FeatureLimitReachedError('product_limit', limit, used);
  }
  
  // Proceed with product creation
}

Error response patterns

Frontend error handling

Present user-friendly error messages in your UI:

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

function ProductCreationForm() {
  const [error, setError] = useState(null);
  const { plans } = useMantle();
  
  const handleSubmit = async (data) => {
    try {
      setError(null);
      await createProduct(data);
      showSuccess('Product created successfully');
    } catch (error) {
      if (error instanceof FeatureNotAvailableError) {
        setError({
          type: 'upgrade',
          message: 'This feature requires a plan upgrade.',
          feature: error.featureKey,
          plans: getPlansWithFeature(plans, error.featureKey)
        });
      } else if (error instanceof FeatureLimitReachedError) {
        setError({
          type: 'limit',
          message: `You've reached your limit of ${error.limit} products.`,
          feature: error.featureKey,
          used: error.used,
          limit: error.limit
        });
      } else {
        setError({
          type: 'generic',
          message: 'An error occurred. Please try again.'
        });
      }
    }
  };
  
  return (
    <div>
      {error && <ErrorBanner error={error} />}
      <ProductForm onSubmit={handleSubmit} />
    </div>
  );
}

API endpoint error responses

Return informative error responses from your API:

app.post('/api/products', async (req, res) => {
  try {
    const product = await createProduct(req.body);
    res.json({ success: true, product });
  } catch (error) {
    if (error instanceof FeatureNotAvailableError) {
      return res.status(403).json({
        code: 'FEATURE_NOT_AVAILABLE',
        message: error.message,
        feature: error.featureKey,
        resolution: 'Upgrade your plan to access this feature'
      });
    } else if (error instanceof FeatureLimitReachedError) {
      return res.status(403).json({
        code: 'FEATURE_LIMIT_REACHED',
        message: error.message,
        feature: error.featureKey,
        limit: error.limit,
        used: error.used,
        resolution: 'Upgrade your plan for a higher limit'
      });
    } else {
      return res.status(500).json({
        code: 'INTERNAL_ERROR',
        message: 'An unexpected error occurred'
      });
    }
  }
});

Error recovery strategies

Graceful degradation

Provide alternative functionality when premium features aren't available:

function AnalyticsReport() {
  const { isFeatureEnabled } = useMantle();
  
  const hasAdvancedAnalytics = isFeatureEnabled({
    featureKey: 'advanced_analytics'
  });
  
  return (
    <div>
      <h2>Analytics Report</h2>
      
      {hasAdvancedAnalytics ? (
        <AdvancedAnalyticsReport />
      ) : (
        <>
          <BasicAnalyticsReport />
          <Banner status="info">
            Upgrade to access advanced analytics with deeper insights.
            <Button onClick={showUpgradeModal}>View Plans</Button>
          </Banner>
        </>
      )}
    </div>
  );
}

Retry logic

Implement retries for transient API errors:

async function checkFeatureWithRetry(featureKey, maxRetries = 3) {
  let retries = 0;
  
  while (retries < maxRetries) {
    try {
      return await mantleClient.isFeatureEnabled({ featureKey });
    } catch (error) {
      if (error.message.includes('network') && retries < maxRetries - 1) {
        // Wait before retrying (with exponential backoff)
        await new Promise(r => setTimeout(r, 1000 * Math.pow(2, retries)));
        retries++;
      } else {
        throw error;
      }
    }
  }
}

Best practices

  1. Be specific - Provide clear, actionable error messages
  2. Offer solutions - Show upgrade paths or alternatives when features aren't available
  3. Gracefully degrade - Provide limited functionality instead of complete failure when possible
  4. Log errors - Track feature-related errors to identify patterns and issues
  5. Handle edge cases - Account for network issues and API failures
  6. Keep error handling consistent - Use similar patterns throughout your application

Next steps


Did this page help you?