Usage Tracking
Track how users interact with your features using Toggly's usage tracking APIs. This enables you to measure feature adoption, calculate conversion rates, and make data-driven rollout decisions.
Overview
Toggly tracks three types of feature interactions:
| Metric | Purpose | Tracking |
|---|---|---|
| Checks | Feature evaluation (decision point) | Automatic |
| Views | User saw the feature (impression) | Manual |
| Usage | User interacted with the feature | Manual |
Getting Started
Inject IFeatureUsageStatsProvider to track views and usage:
public class MyController : Controller
{
private readonly IFeatureUsageStatsProvider _usageStats;
public MyController(IFeatureUsageStatsProvider usageStats)
{
_usageStats = usageStats;
}
}
Recording Feature Views
Call RecordViewAsync when your feature UI is rendered/displayed to the user:
public async Task<IActionResult> Dashboard()
{
// Record that the user SAW the new dashboard
await _usageStats.RecordViewAsync("new-dashboard");
return View();
}
With User Context
Pass context for unique user tracking:
await _usageStats.RecordViewAsync("new-dashboard", new { UserId = User.Identity.Name });
Using the FeatureView Attribute
For MVC controllers and Razor Pages, use the [FeatureView] attribute. This attribute combines gating with tracking - it checks if the feature is enabled (like [FeatureGate]) and only records the view when enabled:
using Toggly.FeatureManagement;
using Microsoft.FeatureManagement.Mvc; // For RequirementType
// No need for [FeatureGate] - FeatureView handles both!
// If feature is disabled, returns 404 (or your IDisabledFeaturesHandler)
[FeatureView("new-dashboard")]
public IActionResult Dashboard()
{
return View();
}
// Multiple features - all must be enabled (default)
[FeatureView("feature-a", "feature-b")]
public IActionResult MultiFeaturePage()
{
return View();
}
// Multiple features - at least one must be enabled
[FeatureView("feature-a", "feature-b", RequirementType = RequirementType.Any)]
public IActionResult EitherFeaturePage()
{
return View();
}
// Using enum-based feature keys
[FeatureView(MyFeatures.NewDashboard)]
public IActionResult Dashboard()
{
return View();
}
For Razor Pages:
[FeatureView("checkout-redesign")]
public class CheckoutModel : PageModel
{
public void OnGet()
{
// Feature is gated AND view is recorded
}
}
Recording Feature Usage
Call RecordUsageAsync when the user actively engages with a feature:
public async Task<IActionResult> SubmitOrder(OrderModel order)
{
// Record that the user USED the new checkout feature
await _usageStats.RecordUsageAsync("new-checkout");
// Process the order...
return RedirectToAction("Confirmation");
}
With User Context
await _usageStats.RecordUsageAsync("new-checkout", new { UserId = User.Identity.Name });
Using the FeatureUsage Attribute
Like [FeatureView], the [FeatureUsage] attribute combines gating with tracking:
using Toggly.FeatureManagement;
using Microsoft.FeatureManagement.Mvc; // For RequirementType
// No need for [FeatureGate] - FeatureUsage handles both!
// If feature is disabled, returns 404 (or your IDisabledFeaturesHandler)
[FeatureUsage("new-checkout")]
public async Task<IActionResult> SubmitOrder(OrderModel order)
{
// Feature is gated AND usage is recorded
return RedirectToAction("Confirmation");
}
// Multiple features - all must be enabled (default)
[FeatureUsage("checkout", "payment-v2")]
public async Task<IActionResult> ProcessPayment()
{
// Both features' usage is recorded
}
// Multiple features - at least one must be enabled
[FeatureUsage("checkout-v1", "checkout-v2", RequirementType = RequirementType.Any)]
public async Task<IActionResult> ProcessPayment()
{
// Usage recorded for whichever feature(s) are enabled
}
Complete Example
Here's a full example showing all three metrics in action:
public class ProductController : Controller
{
private readonly IFeatureManagerSnapshot _featureManager;
private readonly IFeatureUsageStatsProvider _usageStats;
public ProductController(
IFeatureManagerSnapshot featureManager,
IFeatureUsageStatsProvider usageStats)
{
_featureManager = featureManager;
_usageStats = usageStats;
}
public async Task<IActionResult> Details(int id)
{
var product = await _productService.GetAsync(id);
// AUTOMATIC CHECK: Is the quick-buy feature enabled?
if (await _featureManager.IsEnabledAsync("quick-buy"))
{
// MANUAL VIEW: User sees the quick-buy button
await _usageStats.RecordViewAsync("quick-buy");
ViewBag.ShowQuickBuy = true;
}
return View(product);
}
// MANUAL USAGE: User clicks the quick-buy button
[FeatureUsage("quick-buy")]
public async Task<IActionResult> QuickBuy(int productId)
{
await _orderService.QuickPurchaseAsync(productId);
return RedirectToAction("Confirmation");
}
}
When to Use Each
Use [FeatureView] When:
- A UI component is rendered (and you want to gate it)
- A page/section becomes visible
- A modal/dialog is opened
- A tab is selected (revealing feature content)
Use [FeatureUsage] When:
- User clicks a button (and you want to gate the action)
- User submits a form
- User completes a workflow
- User takes a meaningful action
Use Manual Methods (RecordViewAsync/RecordUsageAsync) When:
- You need to track without gating
- The feature check happens elsewhere in your code
- You're tracking in non-controller code (services, views)
Don't Record:
- Hover events (too noisy)
- Scroll events (unless specifically needed)
- Background operations
API Reference
IFeatureUsageStatsProvider
public interface IFeatureUsageStatsProvider
{
// Record a feature check (auto-tracked, but available for manual use)
Task RecordCheckAsync(string featureKey, bool allowed);
// Record a feature view (impression)
Task RecordViewAsync(string featureKey);
Task RecordViewAsync<TContext>(string featureKey, TContext context);
// Record feature usage (interaction)
Task RecordUsageAsync(string featureKey);
Task RecordUsageAsync<TContext>(string featureKey, TContext context);
}
Attributes
Both attributes gate AND track - they check features before allowing execution:
// Gate + record view when action/page handler executes
[FeatureView("feature-key")]
[FeatureView("feature-a", "feature-b")] // Multiple (All required)
[FeatureView("feature-a", "feature-b", RequirementType = RequirementType.Any)]
[FeatureView(MyEnum.Feature)] // Enum support
// Gate + record usage when action/page handler executes
[FeatureUsage("feature-key")]
[FeatureUsage("feature-a", "feature-b")] // Multiple (All required)
[FeatureUsage("feature-a", "feature-b", RequirementType = RequirementType.Any)]
[FeatureUsage(MyEnum.Feature)] // Enum support
Unlike [FeatureGate] which only gates, [FeatureView] and [FeatureUsage] both gate AND track. You don't need to combine them with [FeatureGate].
Handling Disabled Features
When a feature is disabled, [FeatureView] and [FeatureUsage] will block the action and return a 404 Not Found response by default. You can customize this behavior by registering an IDisabledFeaturesHandler:
// In Program.cs or Startup.cs
services.AddSingleton<IDisabledFeaturesHandler, CustomDisabledHandler>();
// Custom handler
public class CustomDisabledHandler : IDisabledFeaturesHandler
{
public Task HandleDisabledFeatures(
IEnumerable<string> features,
ActionExecutingContext context)
{
// Redirect to a "coming soon" page
context.Result = new RedirectToActionResult("ComingSoon", "Home", null);
return Task.CompletedTask;
}
}
Viewing Your Data
Feature usage data appears in the Toggly dashboard:
- Navigate to Features → Select a feature
- Click the Usage tab
- View charts for:
- Check count (enabled vs disabled)
- View count
- Usage count
- Unique users
You can also calculate conversion rates:
- View Rate = Views ÷ Enabled Checks
- Conversion Rate = Usage ÷ Views
Next Steps
- Learn about Feature Usage Tracking concepts
- Set up Monitoring and Anomaly Detection
- Configure Experiments with usage data