Skip to main content
Learn about all available configuration options for self-hosting Better Auth Studio.

StudioConfig Type

The StudioConfig type defines all configuration options for Better Auth Studio:
import type { StudioConfig } from "better-auth-studio";

const config: StudioConfig = {
  auth,           // Required: Your Better Auth instance
  basePath,       // Required: URL path where studio is mounted
  access,         // Optional: Access control
  metadata,       // Optional: Branding and UI customization
  lastSeenAt,     // Optional: Last-seen tracking
  ipAddress,      // Optional: IP geolocation
  events,         // Optional: Event tracking
  tools,          // Optional: Tools configuration
};

Required Options

auth

Type: BetterAuth Your Better Auth instance. This is required for the studio to access your authentication data.
import { auth } from "./lib/auth";

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
};

basePath

Type: string The URL path where the studio is mounted. Must match the route where you mount the handler.
const config: StudioConfig = {
  auth,
  basePath: "/api/studio", // Access at http://localhost:3000/api/studio
};
The basePath must match your framework’s route path. For example, if using Next.js with app/api/studio/[[...path]]/route.ts, set basePath: "/api/studio".

Access Control

access

Type: StudioAccessConfig Control who can access the studio interface.
type StudioAccessConfig = {
  roles?: string[];          // Array of allowed user roles
  allowEmails?: string[];    // Array of allowed email addresses
  sessionDuration?: number;  // Session duration in seconds
  secret?: string;           // Secret for session encryption
};

access.roles

Restrict access to users with specific roles:
const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  access: {
    roles: ["admin", "super-admin"],
  },
};

access.allowEmails

Restrict access to specific email addresses:
const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  access: {
    allowEmails: [
      "[email protected]",
      "[email protected]",
      "[email protected]",
    ],
  },
};

Combining Roles and Emails

You can combine both for flexible access control:
const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  access: {
    roles: ["admin"],
    allowEmails: ["[email protected]"], // This user gets access even without admin role
  },
};

Metadata & Branding

metadata

Type: StudioMetadata Customize the studio’s appearance and branding.
type StudioMetadata = {
  title?: string;
  logo?: string;
  favicon?: string;
  company?: {
    name?: string;
    website?: string;
    supportEmail?: string;
  };
  theme?: "dark" | "light" | "auto";
  colors?: {
    primary?: string;
    secondary?: string;
    accent?: string;
  };
  features?: {
    users?: boolean;
    sessions?: boolean;
    organizations?: boolean;
    analytics?: boolean;
    tools?: boolean;
    security?: boolean;
  };
  links?: Array<{ label: string; url: string }>;
  custom?: Record<string, any>;
};

Basic Branding

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  metadata: {
    title: "Acme Admin Dashboard",
    theme: "dark",
  },
};

Full Branding

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  metadata: {
    title: "Acme Admin Dashboard",
    logo: "/logo.png",
    favicon: "/favicon.ico",
    company: {
      name: "Acme Inc.",
      website: "https://acme.com",
      supportEmail: "[email protected]",
    },
    theme: "dark",
    colors: {
      primary: "#6366f1",
      secondary: "#8b5cf6",
      accent: "#ec4899",
    },
    links: [
      { label: "Documentation", url: "https://docs.acme.com" },
      { label: "Support", url: "https://support.acme.com" },
    ],
  },
};

Feature Toggles

Control which sections are visible:
const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  metadata: {
    features: {
      users: true,
      sessions: true,
      organizations: true,
      analytics: true,
      tools: false,      // Hide tools section
      security: false,   // Hide security section
    },
  },
};

Last-Seen Tracking

lastSeenAt

Type: StudioLastSeenAtConfig Enable automatic last-seen tracking for users.
type StudioLastSeenAtConfig = {
  enabled?: boolean;
  columnName?: string;  // Default: "lastSeenAt"
};
When enabling lastSeenAt, you must add the column to your user table and run migrations.

Basic Setup

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  lastSeenAt: {
    enabled: true,
  },
};
Then run your database migration:
prisma migrate dev

Custom Column Name

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  lastSeenAt: {
    enabled: true,
    columnName: "last_seen_at", // Use snake_case column name
  },
};
The studio injects hooks into Better Auth that automatically update the last-seen timestamp on sign-in and session creation.

IP Geolocation

ipAddress

Type: StudioIpAddressConfig Configure IP geolocation for Events and Sessions.
type StudioIpAddressConfig =
  | {
      provider: "ipinfo";
      apiToken?: string;
      baseUrl?: string;
      endpoint?: "lite" | "lookup";  // Default: "lookup"
    }
  | {
      provider: "ipapi";
      apiToken?: string;
      baseUrl?: string;
    }
  | {
      provider: "static";
      path: string;  // Path to .mmdb file
    };

Using IPInfo

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  ipAddress: {
    provider: "ipinfo",
    apiToken: process.env.IPINFO_TOKEN,
    endpoint: "lookup", // "lite" for free tier (country only), "lookup" for paid (city/region)
  },
};

Using IPApi

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  ipAddress: {
    provider: "ipapi",
    apiToken: process.env.IPAPI_TOKEN,
  },
};

Using MaxMind GeoLite2 (Static)

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  ipAddress: {
    provider: "static",
    path: "./data/GeoLite2-City.mmdb",
  },
};

Event Tracking

events

Type: EventsConfig Configure authentication event tracking and analytics.
type EventsConfig = {
  enabled?: boolean;
  tableName?: string;
  provider?: EventIngestionProvider;
  client?: any;
  clientType?: "postgres" | "prisma" | "drizzle" | "clickhouse" | "sqlite" | "node-sqlite" | "https" | "custom";
  include?: AuthEventType[];
  exclude?: AuthEventType[];
  batchSize?: number;
  flushInterval?: number;
  retryOnError?: boolean;
  liveMarquee?: LiveMarqueeConfig;
  onEventIngest?: (event: AuthEvent) => void | Promise<void>;
};

Basic Event Tracking

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  events: {
    enabled: true,
    tableName: "auth_events",
  },
};

Filter Events

Track only specific event types:
const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  events: {
    enabled: true,
    tableName: "auth_events",
    include: ["sign-in", "sign-up", "sign-out", "password-reset"],
  },
};
Or exclude specific events:
const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  events: {
    enabled: true,
    tableName: "auth_events",
    exclude: ["token-refresh", "session-check"],
  },
};

Live Marquee

Configure the live events marquee:
const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  events: {
    enabled: true,
    liveMarquee: {
      enabled: true,
      pollInterval: 2000,        // Poll every 2 seconds
      speed: 0.5,                // Animation speed
      pauseOnHover: true,        // Pause when hovered
      limit: 50,                 // Show max 50 events
      sort: "desc",              // Newest first
      timeWindow: {
        since: "1h",             // Show events from last hour
      },
      colors: {
        success: "#10b981",
        info: "#3b82f6",
        warning: "#f59e0b",
        error: "#ef4444",
        failed: "#dc2626",
      },
    },
  },
};

Custom Time Window

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  events: {
    enabled: true,
    liveMarquee: {
      enabled: true,
      timeWindow: {
        custom: 2 * 60 * 60, // Custom 2 hours in seconds
      },
    },
  },
};

Event Callback

Run custom logic when events are ingested:
const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  events: {
    enabled: true,
    onEventIngest: async (event) => {
      // Log to external service
      await logToAnalytics(event);
      
      // Send alerts for failed logins
      if (event.type === "sign-in" && event.status === "failed") {
        await sendAlert(event);
      }
    },
  },
};

Tools Configuration

tools

Type: ToolsConfig Control which tools are shown in the studio interface.
type ToolsConfig = {
  exclude?: StudioToolId[];
};

type StudioToolId =
  | "test-oauth"
  | "hash-password"
  | "run-migration"
  | "test-db"
  | "validate-config"
  | "health-check"
  | "export-data"
  | "jwt-decoder"
  | "token-generator"
  | "plugin-generator"
  | "uuid-generator"
  | "password-strength"
  | "oauth-credentials"
  | "secret-generator";

Hide Tools in Production

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  tools: {
    exclude: [
      "test-oauth",
      "test-db",
      "run-migration",
      "health-check",
    ],
  },
};

Environment-Based Configuration

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  tools: {
    exclude: process.env.NODE_ENV === "production"
      ? ["test-oauth", "run-migration", "test-db"]
      : [],
  },
};

Complete Example

Here’s a comprehensive configuration example:
studio.config.ts
import type { StudioConfig } from "better-auth-studio";
import { auth } from "./lib/auth";

const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  
  // Access control
  access: {
    roles: ["admin", "super-admin"],
    allowEmails: ["[email protected]"],
  },
  
  // Branding
  metadata: {
    title: "Acme Admin Dashboard",
    logo: "/logo.png",
    theme: "dark",
    company: {
      name: "Acme Inc.",
      website: "https://acme.com",
      supportEmail: "[email protected]",
    },
    colors: {
      primary: "#6366f1",
      secondary: "#8b5cf6",
      accent: "#ec4899",
    },
  },
  
  // Last-seen tracking
  lastSeenAt: {
    enabled: true,
  },
  
  // IP geolocation
  ipAddress: {
    provider: "ipinfo",
    apiToken: process.env.IPINFO_TOKEN,
    endpoint: "lookup",
  },
  
  // Event tracking
  events: {
    enabled: true,
    tableName: "auth_events",
    include: ["sign-in", "sign-up", "sign-out", "password-reset"],
    liveMarquee: {
      enabled: true,
      pollInterval: 2000,
      speed: 0.5,
      pauseOnHover: true,
      timeWindow: { since: "1h" },
    },
    onEventIngest: async (event) => {
      if (event.type === "sign-in" && event.status === "failed") {
        await sendAlert(event);
      }
    },
  },
  
  // Hide sensitive tools in production
  tools: {
    exclude: process.env.NODE_ENV === "production"
      ? ["test-oauth", "run-migration", "test-db"]
      : [],
  },
};

export default config;

Environment Variables

Recommended environment variables for production:
.env
# Database
DATABASE_URL=postgresql://user:pass@host:5432/db

# IP Geolocation
IPINFO_TOKEN=your_ipinfo_token
IPAPI_TOKEN=your_ipapi_token

# Better Auth
BETTER_AUTH_SECRET=your_secret_key

# Studio
STUDIO_ACCESS_SECRET=your_studio_secret
Use in your config:
const config: StudioConfig = {
  auth,
  basePath: "/api/studio",
  access: {
    secret: process.env.STUDIO_ACCESS_SECRET,
  },
  ipAddress: {
    provider: "ipinfo",
    apiToken: process.env.IPINFO_TOKEN,
  },
};

Next Steps

Next.js Setup

Set up with Next.js App Router

Express Setup

Integrate with Express.js

Build docs developers (and LLMs) love