Usage Tracking Plugin
Track API usage, enforce limits, and manage feature access at the organization level
Usage Tracking Plugin
Package: @bettercone/better-auth-plugin-usage-tracking
Track API usage, enforce limits, and manage feature access at the organization level with Better Auth.
This is a separate package from @bettercone/ui. Install it independently to add usage tracking to your Better Auth setup.
Features
- 🎯 Automatic API Tracking - Tracks all Better Auth API calls per organization
- 📊 Usage History - Complete historical data for analytics and charts
- 🔄 Flexible Reset Modes - Lazy (on-demand), Scheduled (cron), Manual (admin)
- 🚨 Smart Callbacks - Get notified at 80% usage and limit exceeded
- 💰 Plan & Features Tracking - Track plan IDs and feature access per organization
- 📈 Usage Analytics - Built-in time-series data tracking with history table
- 🔒 Secure - Session auth for users, API key for scheduled resets
- 📦 Type-Safe - Full TypeScript support with declarations
Installation
pnpm add @bettercone/better-auth-plugin-usage-trackingnpm install @bettercone/better-auth-plugin-usage-trackingyarn add @bettercone/better-auth-plugin-usage-trackingQuick Start
1. Server Setup
Add the plugin to your Better Auth configuration:
import { betterAuth } from "better-auth";
import { usageTracking } from "@bettercone/better-auth-plugin-usage-tracking";
export const auth = betterAuth({
database: /* your database config */,
plugins: [
usageTracking({
resetMode: "lazy",
resetPeriod: "monthly",
defaultApiLimit: 1000,
onLimitExceeded: async (context) => {
console.log(`Org ${context.organizationId} exceeded limit!`);
},
}),
],
});2. Client Setup
Add the client plugin to your auth client:
import { createAuthClient } from "better-auth/react";
import { usageTrackingClient } from "@bettercone/better-auth-plugin-usage-tracking/client";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_API_URL,
plugins: [usageTrackingClient()],
});3. Database Migration
Run Better Auth CLI to generate and apply migrations:
npx @better-auth/cli@latest generate
npx @better-auth/cli@latest migrateThis adds usage tracking fields to your organization table and creates a usageHistory table.
4. Use with UI Components
The plugin works seamlessly with @bettercone/ui components. Install both packages for the complete experience.
import { ApiUsageCard, UsageDashboard } from "@bettercone/ui";
import { authClient } from "./auth-client";
export default function Dashboard() {
return (
<AuthUIProvider authClient={authClient}>
<ApiUsageCard />
<UsageDashboard />
</AuthUIProvider>
);
}Configuration
Plugin Options
Prop
Type
Configuration Examples
usageTracking({
resetMode: "scheduled",
resetPeriod: "monthly",
defaultApiLimit: 10000,
apiKey: process.env.USAGE_TRACKING_API_KEY,
onLimitExceeded: async ({ organizationId, current, limit }) => {
await sendEmail({
to: await getOrgAdminEmail(organizationId),
subject: "API Limit Exceeded",
body: `Your organization has exceeded its API limit (${current}/${limit}).`,
});
},
onUsageWarning: async ({ organizationId, percentage }) => {
await sendEmail({
to: await getOrgAdminEmail(organizationId),
subject: `API Usage at ${percentage}%`,
body: `Your organization has reached ${percentage}% of its API limit.`,
});
},
})usageTracking({
resetMode: "lazy",
resetPeriod: "daily",
defaultApiLimit: 1000,
onLimitExceeded: async ({ organizationId }) => {
console.log(`Org ${organizationId} exceeded limit`);
},
})usageTracking({
resetMode: "manual",
defaultApiLimit: 100000,
onUsageWarning: async ({ organizationId, percentage }) => {
await notifyAdmin({
orgId: organizationId,
message: `Organization at ${percentage}% usage`,
});
},
})Reset Modes
Lazy Reset (Recommended)
Usage resets automatically on the first API call after the reset date.
usageTracking({ resetMode: "lazy", resetPeriod: "monthly" })Pros: Zero configuration, no cron jobs needed
Cons: Reset happens on first API call (small delay)
Best for: Small to medium SaaS applications
Scheduled Reset
Usage resets at exact scheduled time via cron job.
usageTracking({
resetMode: "scheduled",
resetPeriod: "monthly",
apiKey: process.env.USAGE_TRACKING_API_KEY,
})Setup cron job:
# Every 1st of month at 00:00 UTC
0 0 1 * * curl -X POST https://yourdomain.com/api/auth/usage-tracking/reset-expired \
-H "Content-Type: application/json" \
-d '{"apiKey":"YOUR_API_KEY"}'Pros: Precise timing, predictable behavior
Cons: Requires cron job setup
Best for: Production applications with strict billing cycles
Manual Reset
Admin manually resets usage via API or dashboard.
usageTracking({ resetMode: "manual" })Pros: Full control, audit trail
Cons: Requires manual intervention
Best for: Enterprise applications with custom billing
Client API
Actions
The client plugin provides type-safe actions:
// Get usage
const usage = await authClient.usageTracking.getUsage();
const orgUsage = await authClient.usageTracking.getUsage({
organizationId: "org_123",
});
// Get usage history
const { history } = await authClient.usageTracking.getUsageHistory();
const limitedHistory = await authClient.usageTracking.getUsageHistory({
limit: 90,
});
// Reset usage (admin only)
await authClient.usageTracking.resetUsage({
organizationId: "org_123",
resetApi: true,
});
// Update limits (admin only)
await authClient.usageTracking.updateLimits({
organizationId: "org_123",
apiLimit: 10000,
resetMode: "scheduled",
resetPeriod: "monthly",
});Reactive Atoms
The client plugin provides nanostores atoms for reactive state:
import { useStore } from "@nanostores/react";
import { authClient } from "./auth-client";
function UsageDisplay() {
// Access atoms
const usage = useStore(authClient.$usageTracking.usageAtom);
const loading = useStore(authClient.$usageTracking.loadingAtom);
const error = useStore(authClient.$usageTracking.errorAtom);
// Fetch data
React.useEffect(() => {
authClient.$usageTracking.fetchUsage();
}, []);
if (loading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
return (
<div>
<p>API Calls: {usage.api.current} / {usage.api.limit}</p>
<p>Plan: {usage.planId}</p>
</div>
);
}UI Integration
Requires @bettercone/ui package installed separately.
ApiUsageCard
Display current API usage with progress bar:
import { ApiUsageCard } from "@bettercone/ui";
// Auto-fetch from context
<ApiUsageCard />
// Custom client
<ApiUsageCard authClient={customAuthClient} />
// Manual props
<ApiUsageCard current={500} limit={1000} />View ApiUsageCard documentation →
UsageDashboard
Complete dashboard with all usage metrics:
import { UsageDashboard } from "@bettercone/ui";
<UsageDashboard />View UsageDashboard documentation →
UsageHistoryChart
Interactive chart with time range controls:
import { UsageHistoryChart } from "@bettercone/ui";
<UsageHistoryChart showTimeRangeControls showChartTypeControls />View UsageHistoryChart documentation →
Usage Patterns
Feature Gating
import { useStore } from "@nanostores/react";
import { authClient } from "./auth-client";
function AdvancedFeature() {
const usage = useStore(authClient.$usageTracking.usageAtom);
const hasAccess = usage?.features?.advanced_analytics === true;
React.useEffect(() => {
authClient.$usageTracking.fetchUsage();
}, []);
if (!hasAccess) {
return <UpgradePrompt feature="Advanced Analytics" />;
}
return <AdvancedAnalyticsDashboard />;
}Usage-Based Billing
function BillingSettings() {
const usage = useStore(authClient.$usageTracking.usageAtom);
const overage = Math.max(0, (usage?.api.current || 0) - (usage?.api.limit || 0));
const overageCost = overage * 0.01; // $0.01 per extra call
return (
<div>
<p>Included: {usage.api.limit} API calls</p>
<p>Used: {usage.api.current}</p>
{overage > 0 && <p>Overage: {overage} calls (${overageCost.toFixed(2)})</p>}
</div>
);
}Troubleshooting
Usage not tracking
Problem: API calls aren't being counted
Solutions:
- Verify plugin is installed on both server and client
- Check database migrations ran successfully
- Ensure organization is set in session
- Check server logs for errors
Reset not working
Problem: Usage isn't resetting after reset date
Solutions:
- Lazy mode: Make an API call to trigger reset
- Scheduled mode: Verify cron job is running and API key is correct
- Manual mode: Call
resetUsageAPI endpoint
Callbacks not firing
Problem: onLimitExceeded or onUsageWarning not called
Solutions:
- Check callback function is async and returns Promise
- Verify organization has reached threshold/limit
- Check server logs for callback errors
- Ensure callbacks don't throw uncaught exceptions