UNPKG

better-auth-feature-flags

Version:

Ship features safely with feature flags, A/B testing, and progressive rollouts - Better Auth plugin for modern release management

925 lines (746 loc) โ€ข 30.3 kB
# Better Auth: Feature Flags [![npm version](https://img.shields.io/npm/v/better-auth-feature-flags.svg)](https://www.npmjs.com/package/better-auth-feature-flags) [![npm downloads](https://img.shields.io/npm/dm/better-auth-feature-flags.svg)](https://www.npmjs.com/package/better-auth-feature-flags) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/kriasoft/better-auth/pulls) [![Discord](https://img.shields.io/discord/643523529131950086?label=Discord&logo=discord)](https://discord.gg/SBwX6VeqCY) [![Sponsor](https://img.shields.io/github/sponsors/koistya?label=Sponsor&logo=github)](https://github.com/sponsors/koistya) Enterprise-grade feature flag management integrated with Better Auth. Control feature rollouts, run A/B tests, and manage user experiences with powerful targeting rules and real-time evaluation. ## Features ### Core Capabilities - ๐Ÿš€ **Dynamic Feature Control** - Enable/disable features without deployments - ๐ŸŽฏ **Advanced Targeting** - Rule-based targeting with complex conditions - ๐Ÿงช **A/B Testing** - Multiple variants with deterministic assignment - ๐Ÿ“Š **Analytics & Tracking** - Built-in usage analytics and performance metrics - ๐Ÿ”„ **Progressive Rollouts** - Percentage-based gradual feature releases - ๐Ÿ”’ **Security First** - Role-based access, audit logging, and context sanitization ### Performance & Scale - โšก **High Performance** - <10ms P50 latency with intelligent caching - ๐Ÿ’พ **Multiple Storage Backends** - Database, Memory, or Redis storage - ๐Ÿ”„ **Smart Polling** - Exponential backoff and jitter for efficient updates - ๐Ÿ“ฆ **Batch Evaluation** - Evaluate multiple flags in a single request - ๐ŸŽฏ **Session-Aware Caching** - Automatic cache invalidation on user changes ### Developer Experience - ๐Ÿ“˜ **Full TypeScript Support** - Type-safe flag evaluation with schema validation - โš›๏ธ **React Integration** - Hooks, components, and providers - ๐Ÿ”ง **Development Tools** - Local overrides, debug mode, and DevTools integration - ๐Ÿข **Multi-Tenancy** - Organization-level flag isolation - ๐Ÿ“ **Comprehensive Audit Trail** - Track all changes with configurable retention ## Installation ```bash # npm npm install better-auth better-auth-feature-flags # bun bun add better-auth better-auth-feature-flags # pnpm pnpm add better-auth better-auth-feature-flags # yarn yarn add better-auth better-auth-feature-flags ``` ### Peer Dependencies - This plugin requires `better-auth` as a peer dependency. - Install a compatible version (e.g., `better-auth@^1.3.13`) to ensure proper type and runtime alignment. ## Quick Start ### 1. Server Setup ```typescript import { betterAuth } from "better-auth"; import { featureFlags } from "better-auth-feature-flags"; const auth = betterAuth({ plugins: [ featureFlags({ storage: "database", // "memory" | "database" | "redis" // Define flags with automatic type inference flags: { "ui.dark-mode": { default: false }, "experiment.new-checkout": { default: "control" }, "config.max-items": { default: 10 }, }, // Analytics configuration analytics: { trackUsage: true, trackPerformance: true, }, // Caching for performance caching: { enabled: true, ttl: 60, // seconds maxSize: 1000, }, // Admin access control adminAccess: { enabled: true, roles: ["admin"], }, // Multi-tenancy (optional) multiTenant: { enabled: false, useOrganizations: false, }, // Audit logging audit: { enabled: true, retentionDays: 90, }, }), ], }); ``` ### 2. Client Setup ```typescript import { createAuthClient } from "better-auth/client"; import { featureFlagsClient } from "better-auth-feature-flags/client"; // Public client (for end users) - automatically inherits server schema types const authClient = createAuthClient({ plugins: [ featureFlagsClient({ // Client-side caching cache: { enabled: true, ttl: 60000, // 60 seconds storage: "localStorage", // or "sessionStorage" or "memory" }, // Real-time updates polling: { enabled: true, interval: 30000, // 30 seconds }, // Context collection contextCollection: { collectDevice: false, collectGeo: false, collectCustomHeaders: false, }, }), ], }); // TypeScript now provides full type safety based on server flag definitions const isDarkMode = await authClient.featureFlags.isEnabled("ui.dark-mode"); // ^? boolean (inferred from server default: false) const checkoutVariant = await authClient.featureFlags.getValue( "experiment.new-checkout", ); // ^? string (inferred from server default: "control") const maxItems = await authClient.featureFlags.getValue("config.max-items"); // ^? number (inferred from server default: 10) ``` #### Admin Client Setup For admin dashboards, use both public and admin plugins: ```typescript import { createAuthClient } from "better-auth/client"; import { featureFlagsClient } from "better-auth-feature-flags/client"; import { featureFlagsAdminClient } from "better-auth-feature-flags/admin"; // Admin client (for management interfaces) const adminClient = createAuthClient({ plugins: [ featureFlagsClient(), // Public evaluation methods featureFlagsAdminClient(), // Admin CRUD operations ], }); ``` ### 3. Using Feature Flags ## API Overview ### Public Endpoints - **POST** `/feature-flags/evaluate` - Body: `{ flagKey: string, context?: object, default?: any, select?: 'value'|'full'|Array<'value'|'variant'|'reason'|'metadata'>, environment?: string, track?: boolean, debug?: boolean, contextInResponse?: boolean }` - Response (default): `{ value: any, variant?: string, reason: string, metadata?: any, evaluatedAt: string, context?: object }` - Response (`select: 'value'`): `{ value: any, evaluatedAt: string, context?: object }` - **POST** `/feature-flags/evaluate-batch` - Body: `{ flagKeys: string[], defaults?: Record<string,any>, context?: object, select?: 'value'|'full'|Array<'value'|'variant'|'reason'|'metadata'>, environment?: string, track?: boolean, debug?: boolean, contextInResponse?: boolean }` - Response: `{ flags: Record<string, EvaluationResult>, evaluatedAt: string, context?: object }` - **POST** `/feature-flags/bootstrap` - Body: `{ context?: object, include?: string[], prefix?: string, select?: 'value'|'full'|Array<'value'|'variant'|'reason'|'metadata'>, environment?: string, track?: boolean, debug?: boolean }` - Server response: `{ flags: Record<string, EvaluationResult>|Record<string, any>, evaluatedAt: string, context: object }` - Client helper `featureFlags.bootstrap()` returns a plain keyโ†’value map for convenience - **POST** `/feature-flags/events` - Body: `{ flagKey: string, event: string, properties?: number|object, timestamp?: string (RFC3339), sampleRate?: number }` - Headers: `Idempotency-Key?: string` - Event tracking for analytics Note - Environment can also be supplied via `x-deployment-ring` header; header takes precedence over body `environment`. - Client `bootstrap()` extracts values; use server API with `select` if you need full result shapes. ### Admin Endpoints - **GET** `/feature-flags/admin/flags` - Query: `{ organizationId?, cursor?, limit?, q?, sort?, type?, enabled?, prefix?, include? }` - Enhanced with filtering and metrics projection - Response: `{ flags: FeatureFlag[], page: { nextCursor?, limit, hasMore } }` - **GET** `/feature-flags/admin/flags/:flagId/stats` - Query: `{ start?, end?, granularity?, timezone?, metrics? }` - Analytics with date range validation (max 90 days) and selective metrics - **GET** `/feature-flags/admin/metrics/usage` - Query: `{ start?, end?, organizationId?, metrics? }` - Organization-level analytics with projection support #### Simple Flag Evaluation ```typescript // High-level client methods (v0.2.x) const isEnabled = await authClient.featureFlags.isEnabled("new-feature"); const value = await authClient.featureFlags.getValue( "config-setting", "default", ); const variant = await authClient.featureFlags.getVariant("ab-test"); // Canonical evaluation API const result = await authClient.featureFlags.evaluate("new-feature", { default: false, context: { userId: "123", plan: "premium" }, }); // Returns: { value: boolean, variant?: string, reason: string } // Batch evaluation for performance const results = await authClient.featureFlags.evaluateMany(["flag1", "flag2"], { context: { userId: "123" }, defaults: { flag1: false, flag2: "default" }, }); // Bootstrap all flags const allFlags = await authClient.featureFlags.bootstrap({ context: { userId: "123" }, }); // Event tracking await authClient.featureFlags.track("new-feature", "viewed", { section: "dashboard", }); ``` #### React Integration ```tsx import { FeatureFlagsProvider, useFeatureFlag, useFeatureFlagValue, useVariant, useTrackEvent, Feature, Variant, } from "better-auth-feature-flags/react"; // Provider setup function App() { return ( <FeatureFlagsProvider client={authClient} fetchOnMount={true} context={{ userId: "user-123", plan: "premium" }} > <YourApp /> </FeatureFlagsProvider> ); } // Using hooks function Component() { const isDarkMode = useFeatureFlag("dark-mode", false); const config = useFeatureFlagValue("ui-config", { theme: "light" }); const variant = useVariant("homepage-test"); const track = useTrackEvent(); const handleClick = () => { track("ui-interaction", "button_click", { component: "header" }); }; return ( <div className={isDarkMode ? "dark" : "light"}> <h1>Theme: {config.theme}</h1> <p>Variant: {variant || "default"}</p> <button onClick={handleClick}>Track Event</button> </div> ); } // Conditional rendering function Page() { return ( <Feature flag="premium-feature" fallback={<FreeVersion />}> <PremiumVersion /> </Feature> ); } // A/B testing with variants function Homepage() { return ( <Variant flag="homepage-test"> <Variant.Case variant="control"> <ClassicHomepage /> </Variant.Case> <Variant.Case variant="variant-a"> <ModernHomepage /> </Variant.Case> <Variant.Default> <DefaultHomepage /> </Variant.Default> </Variant> ); } // Suspense support for modern React apps import { FeatureSuspense, useFeatureFlagSuspense, } from "better-auth-feature-flags/react"; function SuspenseExample() { return ( <Suspense fallback={<Loading />}> <FeatureSuspense flag="new-feature" fallback={<OldFeature />}> <NewFeature /> </FeatureSuspense> </Suspense> ); } function SuspenseHook() { // Throws promise for Suspense to catch const isEnabled = useFeatureFlagSuspense("feature-name", false); return <div>Feature is {isEnabled ? "enabled" : "disabled"}</div>; } ``` ### Client Configuration ```typescript import { createAuthClient } from "better-auth/client"; import { featureFlagsClient } from "better-auth-feature-flags/client"; const authClient = createAuthClient({ plugins: [ featureFlagsClient({ // Client-side caching cache: { enabled: true, ttl: 60000, // 60 seconds storage: "localStorage", // or "sessionStorage" or "memory" }, // Automatic polling for updates polling: { enabled: true, interval: 30000, // 30 seconds }, // Context collection contextCollection: { collectDevice: false, collectGeo: false, collectCustomHeaders: false, }, // Development overrides (disabled in production) overrides: { enabled: process.env.NODE_ENV === "development", }, }), ], }); ``` ## Client API Reference ### Core Evaluation Methods ```typescript // Simple boolean check const isEnabled = await authClient.featureFlags.isEnabled("new-feature"); // Get any value type with default const config = await authClient.featureFlags.getValue("ui-config", { theme: "light", sidebar: "collapsed", }); // Get variant for A/B testing const variant = await authClient.featureFlags.getVariant("homepage-test"); // Full evaluation with metadata const result = await authClient.featureFlags.evaluate("feature-name", { default: false, context: { plan: "premium", region: "us-west" }, select: "full", // Returns { value, variant?, reason } }); // Batch evaluation const results = await authClient.featureFlags.evaluateMany( ["feature-1", "feature-2"], { context: { userId: "123" }, defaults: { "feature-1": false, "feature-2": "default" }, }, ); // Bootstrap all enabled flags const allFlags = await authClient.featureFlags.bootstrap({ context: { userId: "123" }, include: ["ui-*", "experiments-*"], // Optional filtering }); ``` ### Event Tracking ```typescript // Simple event tracking await authClient.featureFlags.track("checkout-flow", "step_completed", { step: "payment", value: 99.99, }); // Context and override management authClient.featureFlags.setContext({ plan: "premium", region: "us" }); const context = authClient.featureFlags.getContext(); // Development overrides (disabled in production) authClient.featureFlags.setOverride("debug-mode", true); authClient.featureFlags.clearOverrides(); // Cache management authClient.featureFlags.clearCache(); await authClient.featureFlags.refresh(); ``` ### Admin API (Separate Bundle) Admin operations use a separate client plugin to keep public bundles lean: ```typescript import { createAuthClient } from "better-auth/client"; import { featureFlagsClient } from "better-auth-feature-flags/client"; import { featureFlagsAdminClient } from "better-auth-feature-flags/admin"; // Admin clients include both public and admin plugins const adminClient = createAuthClient({ plugins: [featureFlagsClient(), featureFlagsAdminClient()], }); // Flag management const flags = await adminClient.featureFlags.admin.flags.list({ q: "search-term", type: "boolean", enabled: true, sort: "-updatedAt", limit: 20, }); // Returns: { flags: FeatureFlag[], page: { nextCursor?, limit, hasMore } } const newFlag = await adminClient.featureFlags.admin.flags.create({ key: "new-checkout", name: "New Checkout Flow", type: "boolean", enabled: true, defaultValue: false, rolloutPercentage: 25, variants: [ { key: "control", value: false, weight: 50 }, { key: "test", value: true, weight: 50 }, ], }); // Rule-based targeting await adminClient.featureFlags.admin.rules.create({ flagId: newFlag.id, priority: 1, conditions: { all: [ { attribute: "plan", operator: "equals", value: "premium" }, { attribute: "region", operator: "in", value: ["us", "ca"] }, ], }, value: true, }); // Analytics with enhanced projection const stats = await adminClient.featureFlags.admin.analytics.stats.get( newFlag.id, { start: "2025-01-01", end: "2025-01-31", granularity: "day", metrics: ["total", "uniqueUsers", "variants"], // Selective metrics }, ); const usage = await adminClient.featureFlags.admin.analytics.usage.get({ start: "2025-01-01", end: "2025-01-31", metrics: ["errorRate", "avgLatency"], // Only get performance metrics }); ``` ### What's New in v0.2.0 - Idempotency support for analytics events - Batch event tracking for performance - Canonical API naming and improved DX - Enhanced TypeScript types and error handling - React Suspense hooks and advanced React helpers ## Migration Guide (v0.1.3 โ†’ v0.2.0) The v0.2.0 release introduces canonical API naming for better consistency and long-term stability. The old methods are deprecated but still supported. API renames: - `getFlag()` โ†’ `evaluate()` - `getFlags()` โ†’ `evaluateMany()` - `getAllFlags()` โ†’ `bootstrap()` - `trackEvent()` โ†’ `track()` Old methods are deprecated but still supported for backward compatibility. ### API Methods Overview (Canonical) | Purpose | Method | | ------------------------- | ------------------------------------------ | | **Flag Evaluation** | `featureFlags.isEnabled()` | | | `featureFlags.getValue()` | | | `featureFlags.getVariant()` | | | `featureFlags.evaluate()` | | | `featureFlags.evaluateMany()` | | | `featureFlags.bootstrap()` | | **Analytics** | `featureFlags.track()` | | | `featureFlags.trackBatch()` | | **Admin Operations** | `featureFlags.admin.flags.list()` | | | `featureFlags.admin.flags.create()` | | | `featureFlags.admin.flags.get()` | | | `featureFlags.admin.flags.update()` | | | `featureFlags.admin.flags.delete()` | | | `featureFlags.admin.flags.enable()` | | | `featureFlags.admin.flags.disable()` | | | `featureFlags.admin.rules.list()` | | | `featureFlags.admin.rules.create()` | | | `featureFlags.admin.rules.get()` | | | `featureFlags.admin.rules.update()` | | | `featureFlags.admin.rules.delete()` | | | `featureFlags.admin.rules.reorder()` | | | `featureFlags.admin.overrides.list()` | | | `featureFlags.admin.overrides.create()` | | | `featureFlags.admin.overrides.get()` | | | `featureFlags.admin.overrides.update()` | | | `featureFlags.admin.overrides.delete()` | | | `featureFlags.admin.audit.list()` | | | `featureFlags.admin.audit.get()` | | | `featureFlags.admin.analytics.stats.get()` | | | `featureFlags.admin.analytics.usage.get()` | | **Context** | `featureFlags.setContext()` | | | `featureFlags.getContext()` | | **Cache Management** | `featureFlags.clearCache()` | | | `featureFlags.refresh()` | | **Development Overrides** | `featureFlags.setOverride()` | | | `featureFlags.clearOverrides()` | ## Advanced Configuration ### Static Flag Definitions Define flags in your server configuration for version control: ```typescript import { betterAuth } from "better-auth"; import { featureFlags } from "better-auth-feature-flags"; const auth = betterAuth({ plugins: [ featureFlags({ storage: "database", // Static flag definitions flags: { "maintenance-mode": { enabled: false, default: false, }, "new-checkout": { enabled: true, default: false, rolloutPercentage: 25, // Gradual rollout targeting: { roles: ["beta-tester"], attributes: { plan: "premium" }, }, }, "homepage-test": { enabled: true, variants: [ { key: "control", value: "classic", weight: 50 }, { key: "variant", value: "modern", weight: 50 }, ], }, }, // Analytics configuration analytics: { trackUsage: true, trackPerformance: true, }, // Multi-tenancy multiTenant: { enabled: true, useOrganizations: true, }, // Admin access control adminAccess: { enabled: true, roles: ["admin", "feature-manager"], }, }), ], }); ``` ### Storage Options ```typescript // Database storage (recommended for production) featureFlags({ storage: "database" }); // Memory storage (development/testing) featureFlags({ storage: "memory" }); // Redis storage (high-scale distributed) featureFlags({ storage: "redis" }); ``` ## Best Practices ### Flag Design โœ… **Do:** - Use descriptive, URL-safe keys: `new-checkout`, `experiment-homepage` - Start with `enabled: false` and safe defaults - Use gradual rollouts: `rolloutPercentage: 10` โ†’ `25` โ†’ `50` โ†’ `100` - Define meaningful variant keys: `control`, `variant-a`, `new-design` - Scope flags by organization in multi-tenant environments โŒ **Don't:** - Include PII or secrets in flag metadata - Change flag keys after deployment (breaks analytics) - Use chaotic on/off toggling (prefer rollout percentages) - Omit weights in variants (must sum to 100) ### Performance Optimization ```typescript // Use caching for better performance featureFlagsClient({ cache: { enabled: true, ttl: 60000, // 1 minute storage: "localStorage", }, // Enable polling for real-time updates polling: { enabled: true, interval: 30000, // 30 seconds }, }); // Batch evaluations when possible const results = await client.featureFlags.evaluateMany([ "feature-1", "feature-2", "feature-3", ]); // Use bootstrap for initial page load const allFlags = await client.featureFlags.bootstrap(); ``` ### Security Considerations - Context sanitization is enabled by default - Production overrides are automatically disabled - Admin operations require proper role-based access - Audit logging tracks all flag changes ### TypeScript Integration The plugin provides two approaches for type safety: **automatic inference** and **explicit schemas**. #### Option 1: Automatic Type Inference (Recommended) ```typescript // Server setup with automatic schema inference from flag definitions const auth = betterAuth({ plugins: [ featureFlags({ storage: "database", flags: { "ui.dark-mode": { default: false }, // inferred as boolean "experiment.homepage": { default: "control" }, // inferred as string "config.max-items": { default: 10 }, // inferred as number "feature.premium-checkout": { default: false }, // inferred as boolean }, }), ], }); // ^? Types are automatically inferred from default values // Client automatically inherits server schema types const client = createAuthClient({ plugins: [featureFlagsClient()], // No manual schema needed! }); // TypeScript provides full type safety with zero configuration const isDark = await client.featureFlags.isEnabled("ui.dark-mode"); // ^? boolean (inferred from server default: false) const variant = await client.featureFlags.getValue("experiment.homepage"); // ^? string (inferred from server default: "control") const maxItems = await client.featureFlags.getValue("config.max-items"); // ^? number (inferred from server default: 10) ``` #### Option 2: Explicit Schema Definition ```typescript // Define your flag schema for complex types interface AppFlags { "ui.dark-mode": boolean; "experiment.homepage": "control" | "variant-a" | "variant-b"; "config.max-items": number; "feature.premium-checkout": boolean; } // Server setup with explicit typed schema const auth = betterAuth({ plugins: [ featureFlags<AppFlags>({ storage: "database", // Optional: define static flags (will be type-checked against AppFlags) flags: { "ui.dark-mode": { default: false }, }, }), ], }); // Type-safe client with explicit schema const client = createAuthClient({ plugins: [featureFlagsClient<AppFlags>()], }); // TypeScript ensures exact type matching const variant = await client.featureFlags.getValue( "experiment.homepage", "control", ); // ^? "control" | "variant-a" | "variant-b" (from AppFlags interface) ``` #### Mixed Approach: Best of Both Worlds ```typescript // Define base schema for complex types interface AppFlags { "experiment.homepage": "control" | "variant-a" | "variant-b"; } // Server combines explicit schema with inferred flags const auth = betterAuth({ plugins: [ featureFlags<AppFlags>({ storage: "database", flags: { // Explicit schema type (union) "experiment.homepage": { default: "control" }, // Auto-inferred types "ui.dark-mode": { default: false }, // โ†’ boolean "config.max-items": { default: 10 }, // โ†’ number }, }), ], }); ``` ## Performance & Security ### Performance Metrics - **Evaluation Latency**: <10ms P50, <100ms P99 - **Throughput**: 100,000+ evaluations/second - **Cache Hit Rate**: >95% with proper configuration - **Bundle Size**: ~5KB minified + gzipped (core + React) ### Security Features - **Context Sanitization**: Automatic PII filtering and validation - **Production Safeguards**: Development overrides disabled in production - **Role-Based Access**: Admin operations require proper authentication - **Audit Trail**: Complete change history with configurable retention - **Multi-Tenant Isolation**: Organization-level flag scoping ## Documentation ๐Ÿ“š **[Full Documentation](https://kriasoft.com/better-auth/feature-flags/overview)** - [Quickstart Guide](https://kriasoft.com/better-auth/feature-flags/quickstart.html) - Get up and running in 5 minutes - [Configuration](https://kriasoft.com/better-auth/feature-flags/configuration.html) - Detailed configuration options - [API Reference](https://kriasoft.com/better-auth/feature-flags/api-reference.html) - Complete API documentation - [Client SDK](https://kriasoft.com/better-auth/feature-flags/client-sdk.html) - Frontend integration guide - [Device Detection](https://kriasoft.com/better-auth/feature-flags/device-detection.html) - Target by device, browser, OS - [Troubleshooting](https://kriasoft.com/better-auth/feature-flags/troubleshooting.html) - Common issues and solutions ## Architecture ### Modular Endpoint Design The feature flags plugin uses a modular architecture for better maintainability and performance: #### Public Endpoints (by functional concern) ``` src/endpoints/public/ โ”œโ”€โ”€ evaluate.ts # Single flag evaluation โ”œโ”€โ”€ evaluate-batch.ts # Batch flag evaluation โ”œโ”€โ”€ bootstrap.ts # Bulk flag initialization โ”œโ”€โ”€ events.ts # Analytics event tracking โ”œโ”€โ”€ config.ts # Public configuration โ””โ”€โ”€ health.ts # Service health checks ``` #### Admin Endpoints (by resource type) ``` src/endpoints/admin/ โ”œโ”€โ”€ flags.ts # Flag CRUD operations โ”œโ”€โ”€ rules.ts # Rule management โ”œโ”€โ”€ overrides.ts # Override management โ”œโ”€โ”€ analytics.ts # Stats and metrics โ”œโ”€โ”€ audit.ts # Audit log access โ””โ”€โ”€ environments.ts # Environment management + data export ``` ### Benefits - **Single Responsibility**: Each module focuses on one concern (200-300 lines) - **Better Tree-Shaking**: Unused admin features don't bloat client bundles - **Easier Testing**: Focused test suites per module - **Independent Development**: Teams can work on different modules without conflicts - **Clear API Surface**: RESTful organization makes the API predictable ### Design Decisions - **Public endpoints** organized by **functional concern** for performance optimization - **Admin endpoints** organized by **REST resource** for consistent management UX - **Shared utilities** in `endpoints/shared.ts` for consistent security and validation - **Composition pattern** in `endpoints/index.ts` to maintain backward compatibility ## Comparison | Feature | Better Auth Feature Flags | LaunchDarkly | Unleash | Flagsmith | | --------------------------- | ------------------------- | ------------ | ---------- | ---------- | | **Open Source** | โœ… | โŒ | โœ… | โœ… | | **Self-hosted** | โœ… | โŒ | โœ… | โœ… | | **Type Safety** | โœ… Full | โš ๏ธ Partial | โš ๏ธ Partial | โš ๏ธ Partial | | **Better Auth Integration** | โœ… Native | โŒ | โŒ | โŒ | | **Smart Caching** | โœ… | โœ… | โš ๏ธ Basic | โš ๏ธ Basic | | **A/B Testing** | โœ… | โœ… | โœ… | โœ… | | **Audit Logging** | โœ… | โœ… | โœ… | โœ… | | **Multi-tenancy** | โœ… | โœ… | โš ๏ธ Limited | โœ… | | **Device Detection** | โœ… | โš ๏ธ Limited | โŒ | โŒ | | **Pricing** | Free | $$$ | Free/$ | Free/$ | ## Support - ๐ŸŽฎ Discord: [Join our community](https://discord.gg/SBwX6VeqCY) - ๐Ÿ’ฌ Discussions: [GitHub Discussions](https://github.com/kriasoft/better-auth/discussions) - ๐Ÿ› Issues: [GitHub Issues](https://github.com/kriasoft/better-auth/issues) - ๐Ÿ’– Sponsor: [GitHub Sponsors](https://github.com/sponsors/koistya) ## Contributing We welcome contributions! Please see our [Contributing Guide](../../.github/CONTRIBUTING.md) for details. ## Sponsors This project is made possible by our generous sponsors. Thank you for your support! ๐Ÿ™ <a href="https://github.com/sponsors/koistya"> <img src="https://img.shields.io/github/sponsors/koistya?style=social" alt="Sponsor @koistya on GitHub" /> </a> ## License MIT - See [LICENSE](./LICENSE) for details