Deploy Better Auth Studio on your own infrastructure.
Self-hosting is currently in beta. This feature allows you to deploy Better Auth Studio alongside your application for production use. You may encounter bugs or incomplete features. Please report any issues on GitHub.
Self-hosting Better Auth Studio allows you to embed the admin dashboard directly into your application. This enables you to access the studio at a custom route like /api/studio or /admin.
Benefits include:
auth.ts configuration⚠️ Important: For self-hosting, install as a regular dependency (not devDependency) since it's needed at runtime in production.
Run the init command to generate the configuration file:
This creates a studio.config.ts file in your project root:
For Next.js App Router, the init command automatically creates the API route file at app/api/studio/[[...path]]/route.ts:
Access the studio at /api/studio
auth(required)Your Better Auth instance from auth.ts
basePath(required)The URL path where studio is mounted (e.g., /api/studio)
⚠️ Important: When adjusting the basePath, make sure to adjust your route structure accordingly when mounting the handler.
For example, if your basePath is /admin, your route file should be at app/admin/[[...path]]/route.ts to matching the path structure.
access.allowEmails(optional)Array of email addresses allowed to access the studio
💡 Best Practice: Use environment variables for configuration to keep sensitive data out of your codebase:
Add to your .env file:
access.roles(optional)Array of user roles allowed to access (e.g., ["admin", "superadmin"])
ipAddress(optional)IP geolocation for Events and Sessions. Set provider to "ipinfo", "ipapi", or "static" (with path to your .mmdb). For ipinfo/ipapi: optional apiToken, baseUrl; ipinfo also supports endpoint: "lite" | "lookup". See the section below for details.
metadata(optional)Custom branding and configuration options for the studio interface
metadata.title(optional)Custom title displayed in the browser tab and application header. Default: "Better Auth Studio"
metadata.logo(optional)URL or path to your custom logo image. Supports external URLs (http/https) or local paths. Will be displayed in the header navbar. Default: "/logo.png"
metadata.favicon(optional)URL or path to your custom favicon. Supports multiple formats: .png, .ico, .svg, .jpg, .webp. Will be displayed in browser tabs. Default: "/logo.png"
metadata.company.name(optional)Your company or organization name displayed in the header navbar. Default: "Better-Auth Studio."
metadata.company.website(optional)Your company website URL. When provided, the company name in the header becomes a clickable link. Opens in a new tab with proper security attributes.
metadata.theme(optional)Theme preference for the studio interface. Options: "light" or "dark". Default: "dark"
metadata.customStyles(optional)Custom CSS styles to inject into the studio interface. Allows for advanced theming and customization beyond the default theme options.
💡 Example: Complete metadata configuration:
events.enabled(optional)Enable event ingestion to track authentication events. When enabled, events are automatically captured and stored in your database. Default: false
events.client(optional)Database client instance (Prisma client, Drizzle instance, Postgres pool, ClickHouse client, etc.)
events.clientType(optional)Type of database client. Options: "prisma", "drizzle", "postgres", "sqlite", "clickhouse", "https"
events.tableName(optional)Name of the table to store events. Default: "auth_events"
events.onEventIngest(optional)Callback function invoked when an event is ingested. Receives the complete event object with all data (type, metadata, userId, etc.) that will be sent to the database. Useful for external actions like webhooks, analytics tracking, or custom logging.
💡 Example: Using onEventIngest callback:
events.liveMarquee(optional)Configuration for the live event marquee displayed at the top of the studio interface
events.liveMarquee.enabled(optional)Enable the live event marquee. Default: true
events.liveMarquee.pollInterval(optional)Polling interval in milliseconds for fetching new events. Default: 2000 (2 seconds)
events.liveMarquee.speed(optional)Animation speed in pixels per frame for the scrolling marquee. Default: 0.5
events.liveMarquee.pauseOnHover(optional)Pause the marquee animation when hovered. Default: true
events.liveMarquee.limit(optional)Maximum number of events to display in the marquee. Default: 50
events.liveMarquee.sort(optional)Sort order for events. Options: "desc" (newest first) or "asc" (oldest first). Default: "desc"
events.liveMarquee.colors(optional)Custom colors for event severity types. Object with optional properties: success, info, warning, error, failed
events.liveMarquee.timeWindow(optional)Time window for fetching events in the marquee. Can be a predefined preset or a custom duration in seconds. Default: "1h"
Options:
since: "15m" | "30m" | "1h" | "2h" | "4h" | "6h" | "12h" | "1d" | "2d" | "3d" | "7d" | "14d" | "30d" - Predefined time windowcustom: number - Custom duration in seconds (e.g., 2 * 60 * 60 for 2 hours)Note: Either since or custom must be provided, but not both.
💡 Example: Complete events configuration:
When enabled, Studio tracks when each user was last active (sign-in or sign-up). The value is shown in the Users list and on the user details page. No Better Auth plugin is required—Studio injects the field and updates it automatically.
Enable in config:
Add a column to your user table with the same name as columnName (default lastSeenAt) as an optional datetime (nullable timestamp), then run your migration (e.g. prisma migrate dev, drizzle-kit push) or update the schema with your database client.
To show IP geolocation (city, country) for Events and Sessions, you can use an external API via ipAddress in your studio.config.ts, or use a local MaxMind GeoLite2 database (no API key).
Supported providers
endpoint: "lite" for free plan, endpoint: "lookup" for city/region)path to your .mmdb (e.g. your own GeoLite2 or ipdb). No API key; Studio figures out location from the file.Example — ipinfo.io:
Example — ipapi.co:
Example — MaxMind GeoLite2 .mmdb (static): If you have a DB (e.g. GeoLite2-City or your own ipdb), point to it in config. Studio will use this path and resolve locations from the file.
Run pnpm geo:update in the studio package to download the default GeoLite2-City.mmdb into ./data/. If you omit ipAddress entirely, Studio falls back to ./data/GeoLite2-City.mmdb when present, then default-geo/ranges. Production: static works in prod as long as the .mmdb file is deployed with your app and path points to its location at runtime (relative to process cwd or absolute).
allowedEmails or allowedRoles in production