Quickstart Guide ​
Get up and running with Better Auth Feature Flags in 5 minutes.
Installation ​
Install the feature flags plugin alongside Better Auth:
bun add better-auth better-auth-feature-flags
npm install better-auth better-auth-feature-flags
pnpm add better-auth better-auth-feature-flags
yarn add better-auth better-auth-feature-flags
Note
better-auth
is a peer dependency of the feature flags plugin.- Install a matching version (e.g.,
better-auth@^1.3.11
) to avoid package resolution issues.
Basic Setup ​
1. Add the Plugin to Your Auth Config ​
Configure the plugin on your server:
// auth.ts
import { betterAuth } from "better-auth";
import { featureFlags } from "better-auth-feature-flags";
export const auth = betterAuth({
plugins: [
featureFlags({
storage: "database", // or "memory" for development
}),
],
});
2. Run Database Migrations ​
If using database storage, create the required tables:
# Generate migration
npx better-auth migrate
# Apply migration to your database
npx better-auth migrate:run
TIP
The plugin creates tables for flags, rules, overrides, and optionally analytics. See Database Schema for details.
3. Initialize Client SDK ​
Set up the client-side SDK:
// auth-client.ts
import { createAuthClient } from "better-auth/client";
import { featureFlagsClient } from "better-auth-feature-flags/client";
export const authClient = createAuthClient({
plugins: [featureFlagsClient()],
});
Client Bundles ​
Keep the public bundle lean and include admin capabilities only where needed:
import { createAuthClient } from "better-auth/client";
import { featureFlagsClient } from "better-auth-feature-flags/client";
import { featureFlagsAdminClient } from "better-auth-feature-flags/admin";
// Public surfaces (no admin)
export const publicClient = createAuthClient({
plugins: [featureFlagsClient()],
});
// Admin surfaces only (add admin plugin)
export const adminClient = createAuthClient({
plugins: [featureFlagsClient(), featureFlagsAdminClient()],
});
Your First Feature Flag ​
Create a Flag ​
Create your first feature flag programmatically:
// Create a simple boolean flag
await auth.api.createFeatureFlag({
body: {
key: "new-feature",
name: "New Feature",
type: "boolean",
enabled: true,
defaultValue: false,
rolloutPercentage: 0,
description: "My first feature flag",
},
});
Evaluate the Flag ​
Check if a feature is enabled:
// Server-side evaluation
app.get("/api/feature-check", async (req, res) => {
const session = await auth.getSession(req);
const result = await auth.api.evaluateFeatureFlag({
body: { flagKey: "new-feature", context: { userId: session?.user?.id } },
});
res.json({ enabled: result.value });
});
// Client-side evaluation
const isEnabled = await authClient.featureFlags.isEnabled("new-feature");
if (isEnabled) {
// Show new feature
console.log("New feature is enabled!");
}
Common Patterns ​
Percentage Rollout ​
Gradually roll out a feature to users:
await auth.api.createFeatureFlag({
body: {
key: "beta-feature",
name: "Beta Feature",
type: "boolean",
enabled: true,
defaultValue: false,
rolloutPercentage: 25,
},
});
User Targeting ​
Target specific users or groups:
// Create flag
const { flag } = await auth.api.createFeatureFlag({
body: {
key: "premium-feature",
name: "Premium Feature",
type: "boolean",
enabled: true,
defaultValue: false,
},
});
// Add targeting rule
await auth.api.createFeatureFlagRule({
body: {
flagId: flag.id,
priority: 0,
conditions: {
all: [
{ attribute: "subscription", operator: "equals", value: "premium" },
],
},
value: true,
},
});
A/B Testing ​
Set up an A/B test with variants:
await auth.api.createFeatureFlag({
body: {
key: "checkout-test",
name: "Checkout Test",
type: "json",
enabled: true,
defaultValue: { buttonColor: "blue", buttonText: "Buy Now" },
variants: [
{
key: "control",
weight: 50,
value: { buttonColor: "blue", buttonText: "Buy Now" },
},
{
key: "variant_a",
weight: 50,
value: { buttonColor: "green", buttonText: "Purchase" },
},
],
},
});
// Get variant key and JSON config
const variantKey = await authClient.featureFlags.getVariant("checkout-test");
const config = await authClient.featureFlags.getValue("checkout-test");
// Use config.buttonColor and config.buttonText
// Or branch on variantKey to control UI
React Integration ​
First, wrap your app with the provider:
import { FeatureFlagsProvider } from "better-auth-feature-flags/react";
import { authClient } from "./auth-client";
function App() {
return (
<FeatureFlagsProvider client={authClient}>
<YourApp />
</FeatureFlagsProvider>
);
}
Then use feature flags in React components:
import { useFeatureFlag } from "better-auth-feature-flags/react";
function MyComponent() {
const isEnabled = useFeatureFlag("new-feature", false);
return <div>{isEnabled ? <NewFeature /> : <OldFeature />}</div>;
}
Or use the component wrapper:
import { Feature } from "better-auth-feature-flags/react";
function App() {
return (
<Feature flag="premium-feature" fallback={<UpgradePrompt />}>
<PremiumContent />
</Feature>
);
}
Admin Dashboard ​
List All Flags ​
Get all flags with their current status:
const { flags } = await auth.api.listFeatureFlags({ query: {} });
flags.forEach((flag) => {
console.log(`${flag.key}: ${flag.enabled ? "ON" : "OFF"}`);
});
Update a Flag ​
Toggle a flag on/off:
// Disable a flag
await auth.api.updateFeatureFlag({
body: { id: "flag-id", enabled: false },
});
// Increase rollout percentage
await auth.api.updateFeatureFlag({
body: { id: "flag-id", rolloutPercentage: 50 },
});
User Override ​
Override a flag for a specific user:
await auth.api.createFeatureFlagOverride({
body: { flagId: "flag-id", userId: "user-123", value: true },
});
Environment-Specific Flags ​
Use different configurations per environment:
// auth.ts
const isDevelopment = process.env.NODE_ENV === "development";
export const auth = betterAuth({
plugins: [
featureFlags({
storage: isDevelopment ? "memory" : "database",
flags: isDevelopment
? {
// Default flags for development
"debug-mode": {
enabled: true,
defaultValue: true,
},
"new-feature": {
enabled: true,
defaultValue: true,
},
}
: undefined,
}),
],
});
Monitoring & Analytics ​
Track Flag Usage ​
Enable analytics to track evaluations:
featureFlags({
analytics: {
trackUsage: true,
trackPerformance: true,
},
});
// Query usage stats
const { stats } = await auth.api.getFeatureFlagStats({
body: { flagId: "flag-id", period: "day" },
});
console.log(`Evaluations: ${stats.evaluationCount}`);
console.log(`Unique users: ${stats.uniqueUsers}`);
Audit Log ​
Track who changed flags and when:
featureFlags({
audit: {
enabled: true,
retentionDays: 90,
},
});
// Query audit log
const { entries } = await auth.api.listFeatureFlagAuditEntries({
body: { flagId: "flag-id", limit: 10 },
});
Best Practices ​
Quick Tips
- Start with memory storage during development, switch to database for production
- Use descriptive flag keys like
feature-checkout-v2
instead oftest1
- Set appropriate defaults that work if the flag system fails
- Clean up old flags to keep your codebase maintainable
- Monitor performance when rolling out to large user bases
Flag Authoring Tips (Do/Don’t) ​
Do
- Use URL‑safe, unique
key
values; avoid renaming after release. - Keep
type
stable; ensuredefaultValue
, rules, and overrides match it. - Start disabled with a safe
defaultValue
; ramp withrolloutPercentage
. - Define
variants
with weights that sum to 100 and meaningful keys. - Add
description
/metadata
for clarity and ownership. - Scope by
organizationId
when using multi‑tenant mode.
Don’t
- Don’t put PII or secrets in
metadata
/key
. - Don’t expect sticky rollout without a stable
userId
in context. - Don’t omit weights when you need non‑equal variant distribution.
- Don’t rely on overrides as long‑term targeting; prefer rules.
Common Issues ​
Flag Not Found ​
If a flag returns not_found
:
- Check the flag key spelling
- Ensure the flag is created
- Verify multi-tenant organization ID if applicable
Evaluation Latency ​
For better performance:
- Enable caching with appropriate TTL
- Use bulk evaluation for multiple flags
- Consider Redis for distributed caching
Type Safety ​
Use TypeScript generics for type-safe evaluations:
// Define your flag types
type MyFlags = {
"dark-mode": boolean;
"max-uploads": number;
"theme-config": { primary: string; secondary: string };
};
// Type-safe evaluation
const isDark =
await authClient.featureFlags.isEnabled<MyFlags["dark-mode"]>("dark-mode");
const maxUploads =
await authClient.featureFlags.getValue<MyFlags["max-uploads"]>("max-uploads");
What's Next? ​
Now that you have feature flags running:
- đź“– Read the Configuration Guide for advanced options
- đź”§ Explore the API Reference for all available methods
- 📱 Check the Client SDK Guide for frontend integration
- 🔍 Review Troubleshooting for common issues
Example Projects ​
Check out these complete examples:
- Basic Setup - Minimal configuration
- React App - React with hooks and components
- Multi-tenant - Organization-based flags
- A/B Testing - Variant testing setup