Skip to main content

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:

MetricPurposeTracking
ChecksFeature evaluation (decision point)Automatic
ViewsUser saw the feature (impression)Manual
UsageUser interacted with the featureManual

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
Combined Behavior

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:

  1. Navigate to Features → Select a feature
  2. Click the Usage tab
  3. 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​