Dashboard

The `dash()` plugin connects your Better Auth instance to Better Auth Infrastructure, enabling analytics tracking, activity monitoring, event logging, and admin dashboard APIs.

The Dashboard plugin is the core connection between your Better Auth instance and Better Auth Infrastructure. It powers the web dashboard with real-time data, tracks user activity, and enables admin APIs.

What the Dashboard Plugin Enables

Once dash() is active, the Better Auth Infrastructure dashboard gives you:

  • User management — view, search, ban, and delete users
  • Session monitoring — see active sessions and revoke them
  • Organization overview — manage organizations and members
  • Analytics — track sign-ups, sign-ins, and active users over time
  • Audit logs — query event history (learn more)

Installation

import { betterAuth } from "better-auth";
import { dash } from "@better-auth/infra";

export const auth = betterAuth({
  plugins: [
    dash(),
  ],
});

Compatibility

@better-auth/infra requires Better Auth 1.4 or later. Actively supported @better-auth/infra releases start at 0.3.

@better-auth/infraBetter Auth 1.41.51.61.7
0.3.x✓✓✓✓
0.4.x✓✓✓✓

Use the latest 0.4.x release with the latest stable Better Auth 1.7.x.

Managed directory sync (Better Auth 1.7+) requires @better-auth/infra 0.4.x.

Configuration Options

DashOptions

OptionTypeDescription
apiUrlstringBetter Auth Infrastructure API URL. Default: https://dash.better-auth.com
kvUrlstringKV store URL. Default: https://kv.better-auth.com
apiKeystringYour API key for authentication. Falls back to BETTER_AUTH_API_KEY in env.
apiOptionsobjectDash API HTTP client options. Accepts timeout?: number (ms, default 3000) and headers?: Record<string, string>.
kvOptionsobjectKV HTTP client options. Accepts timeout?: number (ms, default 1000), retry?: { attempts?: number; baseDelay?: number; maxDelay?: number } (defaults 2 / 400 / 600), and headers?: Record<string, string>.
apiTimeoutnumberDeprecated alias for apiOptions.timeout.
kvTimeoutnumberDeprecated alias for kvOptions.timeout.
activityTrackingobjectActivity tracking configuration.
managedDirectorySyncobjectManaged directory-sync control-plane options.

Activity Tracking

Track when users were last active in your application. When enabled, a lastActiveAt field is automatically updated on user activity.

dash({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  activityTracking: {
    enabled: true,
    updateInterval: 300000,  // Update interval in ms (default: 5 minutes)
  },
}),

Schema Changes

When activity tracking is enabled, the plugin adds a field to your user schema:

user: {
  fields: {
    lastActiveAt: {
      type: "date",
    },
  },
}

Enabling activity tracking requires a database migration for the lastActiveAt user field. Run the migration step before relying on activity data.

npx auth@latest migrate

If your app manages schema generation separately, run npx auth@latest generate and apply the generated migration with your preferred tool.

Managed Directory Sync

Managed directory sync is the dashboard-side companion to the SCIM plugin-managed connections. Use it when each organization (tenant) should get a directory connection from the dashboard — without hard-coding every tenant in application config.

In Better Auth 1.7, SCIM supports three connection modes:

  • static code-defined connections
  • application-owned runtime verification
  • plugin-managed runtime connections via managedConnections

Managed directory sync is built for that third mode. Responsibilities split cleanly:

LayerOwns
scim({ managedConnections })SCIM protocol, credential catalog, user/group provisioning
dash({ managedDirectorySync })Per-organization connection lifecycle, dashboard APIs, and projection into your app

Once the control plane is on, you can layer optional behaviors: project users into organization memberships and pair the directory with SSO for sign-in, map SCIM attributes onto custom fields.

Enable managed directory sync

You need the organization, SCIM, and dash plugins. SCIM must use managedConnections so Infrastructure can reserve and issue per-tenant credentials.

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { sso } from "@better-auth/sso";
import { scim } from "@better-auth/scim";
import { dash } from "@better-auth/infra";

export const auth = betterAuth({
  plugins: [
    organization(),
    sso({}),
    scim({
      connections: [],
      managedConnections: {
        credentialHashSecret: process.env.SCIM_CREDENTIAL_HASH_SECRET!,
      },
    }),
    dash({
      apiKey: process.env.BETTER_AUTH_API_KEY,
      managedDirectorySync: {
        enabled: true,
      },
    }),
  ],
});

Enabling managedDirectorySync adds reservation tables. Run a migration before creating directories from the dashboard.

npx auth@latest migrate

If your app manages schema generation separately, run npx auth@latest generate and apply the generated migration with your preferred tool.

After setup, create and manage directories in the Infrastructure dashboard (or via the directory sync endpoints).

Membership projection

Membership projection is the usual next step after a directory is connected: when SCIM provisions or deactivates a user, dash reconciles that user into the organization that owns the directory.

It is enabled by default whenever managed directory sync is on.

dash({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  managedDirectorySync: {
    enabled: true,
    membershipProjection: {
      enabled: true,
      role: "member", // role only when projection creates the membership
    },
  },
})
OptionTypeDescription
membershipProjection.enabledbooleanInstalls SCIM projection.reconcileUser for org membership. Default: true.
membershipProjection.rolestringRole assigned when projection creates a membership. Default: "member".

Behavior:

  • Active directory users are added to the organization (or linked if they are already members).
  • Deactivated users have directory-owned memberships removed.
  • Provenance tracks whether the membership was created by the directory or only observed (already existed). Projection only mutates memberships it owns.

Turn it off only when SCIM should provision users without touching organization membership:

membershipProjection: {
  enabled: false,
}

SSO pairing

SSO pairing links a managed directory to an SSO provider so a provisioned user can sign in with the same identity the directory manages.

It is enabled by default. You only need it when directories are created as paired with SSO.

plugins: [
  sso({}),
  dash({
    apiKey: process.env.BETTER_AUTH_API_KEY,
    managedDirectorySync: {
      enabled: true,
      ssoPairing: true,
    },
  }),
]
OptionTypeDescription
ssoPairingbooleanInstalls SSO pairing hooks. Default: true.

Notes:

  • Paired sign-in resolves the Better Auth user from the SCIM external id configured on the directory.
  • Set ssoPairing: false when you only need provisioning and never create paired directories.

Field projection

Field projection maps SCIM User attributes onto Better Auth fields during directory-sync reconcile. Mappings are configured per directory connection in the Infrastructure dashboard (or via the field-mapping endpoints).

It is opt-in and adds its own tables — enable it, migrate, then configure mappings.

auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { scim } from "@better-auth/scim";
import { dash } from "@better-auth/infra";

export const auth = betterAuth({
  user: {
    additionalFields: {
      employeeId: { type: "string", required: false },
      department: { type: "string", required: false },
    },
  },
  plugins: [
    organization({
      schema: {
        member: {
          additionalFields: {
            title: { type: "string", required: false },
            costCenter: { type: "string", required: false },
          },
        },
      },
    }),
    scim({
      connections: [],
      managedConnections: {
        credentialHashSecret: process.env.SCIM_CREDENTIAL_HASH_SECRET!,
      },
    }),
    dash({
      apiKey: process.env.BETTER_AUTH_API_KEY,
      managedDirectorySync: {
        enabled: true,
        fieldProjection: {
          enabled: true,
        },
      },
    }),
  ],
});
  1. Declare the user / member fields you want to fill (as above).
  2. Run migrations for the field-mapping tables.
  3. In the Infrastructure dashboard, open the directory connection and map SCIM paths → target fields.
OptionTypeDescription
fieldProjection.enabledbooleanAdds field-mapping schema and projects fields on SCIM reconcile. Default: false.

Projectable fields

EntityProjectable targetsNot projectable
userWritable user.additionalFields, plus allowlisted core field imageIdentity fields managed by SCIM (email, name); sensitive keys (role, password, ban fields, 2FA secrets); input: false or reference fields
memberWritable organization.schema.member.additionalFields onlyMembership-owned keys (role, user/org ids). Member maps require membership projection (default on)

Only directory-created memberships (ownership: "created") receive member field projection. Observed memberships are left unchanged.

SCIM attribute catalog

Mappings read from a curated SCIM User attribute catalog:

PathTypical use
userNameLogin / username
primaryEmail / emails.primaryEmail-shaped additional fields
displayNameDisplay name
externalIdIdP stable id
name.formatted / name.givenName / name.familyNameName parts
titleJob title
userTypeUser classification
preferredLanguage / locale / timezoneLocale preferences
photos.primaryAvatar URL → often mapped to user.image
enterprise.employeeNumberEmployee id
enterprise.costCenter / enterprise.organization / enterprise.division / enterprise.departmentEnterprise extension attributes

Values are resolved as strings from the persisted SCIM user row, then coerced to the Better Auth field type (string, number, boolean, or date).

Projection modes

Each connection stores a mode per entity (user and member). Default mode is create-only.

ModeBehavior
create-onlyFills empty targets (and values that still match a static schema default) on first provision. Once every productive mapped target is set, projection freezes for that row so later operator edits are preserved. Delayed SCIM attributes can still land until freeze.
reconcileRewrites mapped fields on every SCIM reconcile while the directory owns the profile / membership. Use when the IdP should remain the source of truth for those fields.

Options reference

Full managedDirectorySync shape:

OptionTypeDescription
enabledbooleanEnables the control plane and reservation tables / APIs. Default: false.
ssoPairingbooleanSSO pairing hooks for paired directories. Default: true.
membershipProjectionobjectSCIM → organization membership projection.
membershipProjection.enabledbooleanDefault: true.
membershipProjection.rolestringRole when projection creates a membership. Default: "member".
fieldProjectionobjectOpt-in SCIM → custom field projection.
fieldProjection.enabledbooleanDefault: false.

Database schema

When managedDirectorySync.enabled is true, the dash plugin adds the models below. Better Auth supplies the primary id field for each model. Some tables are only installed when an opt-in feature is enabled.

directorySyncConnection

Always present when managed directory sync is enabled. Stores the managed directory's organization, SCIM connection, SSO pairing, lifecycle, and decommission state.

ColumnTypeConstraintsDescription
organizationIdstringRequired, indexedOrganization that owns the directory.
providerIdstringRequiredSSO provider identifier.
aliasKeystringRequired, unique, hiddenInternal directory alias.
provisioningDomainIdstringRequired, uniqueSCIM provisioning domain.
activeOrganizationKeystringRequired, unique, hiddenActive organization key for reconciliation.
connectionIdstringOptional, uniqueManaged SCIM connection identifier.
creationRequestIdstringRequired, unique, hiddenImmutable ownership correlation value.
statusstringRequiredCurrent directory lifecycle status.
revisionnumberRequired, default 0, hiddenMutation revision used for concurrency control.
createdAtdateRequiredCreation timestamp.
createdByActorIdstringRequiredActor that created the directory.
updatedAtdateRequiredLast update timestamp.
lastActorIdstringRequiredActor responsible for the latest mutation.
ssoProviderIdstringOptionalPaired SSO provider ID.
ssoProviderRecordIdstringOptional, indexedPaired SSO provider record.
activeSsoProviderKeystringRequired, unique, hiddenActive SSO provider key.
serializedSsoPairingstringOptional, hiddenSerialized SSO pairing metadata.
pairingEnforcedbooleanRequired, default falseWhether the SSO pairing requirement is enforced.
unpairedAtdateOptionalTime at which the directory was unpaired.
unpairedBystringOptionalActor that removed the pairing.
decommissionStartedAtdateOptionalTime decommissioning started.
decommissionedAtdateOptionalTime decommissioning completed.
lastErrorstringOptional, hiddenMost recent reconciliation error.

directorySyncMembershipProvenance

Always present when managed directory sync is enabled. Tracks organization memberships created or managed by membership projection.

ColumnTypeConstraintsDescription
membershipKeystringRequired, unique, hiddenStable key for the projected membership.
organizationIdstringRequired, indexedOrganization containing the membership.
userIdstringRequired, indexedBetter Auth user receiving the membership.
memberIdstringRequired, uniqueOrganization member record identifier.
ownershipstringRequired, hiddencreated or observed.
provisioningDomainIdstringRequired, indexedSCIM provisioning domain that produced it.
createdAtdateRequiredCreation timestamp.
updatedAtdateRequiredLast update timestamp.

directorySyncFieldMapping

Only when fieldProjection.enabled is true. Per-connection mapping from SCIM attributes (and/or static defaults) onto Better Auth fields.

ColumnTypeConstraintsDescription
mappingKeystringRequired, unique, hiddenStable key for the mapping row.
provisioningDomainIdstringRequired, indexedSCIM provisioning domain.
organizationIdstringRequired, indexedOrganization that owns the directory.
entitystringRequiredTarget entity (user or member).
targetFieldstringRequiredBetter Auth field name on that entity.
scimSourcestringOptionalSCIM resource for scimPath (defaults from entity).
scimPathstringOptionalAllowlisted SCIM attribute path.
defaultValuestringOptionalFallback when SCIM value is empty.
enabledbooleanRequired, default trueWhether the mapping is applied at runtime.
createdAtdateRequiredCreation timestamp.
updatedAtdateRequiredLast update timestamp.
createdByActorIdstringRequiredActor that created the mapping.
lastActorIdstringRequiredActor responsible for the latest mutation.

directorySyncFieldProjectionMode

Only when fieldProjection.enabled is true. Per-connection, per-entity write cadence (create-only or reconcile).

ColumnTypeConstraintsDescription
modeKeystringRequired, unique, hiddenStable key for the mode row.
provisioningDomainIdstringRequired, indexedSCIM provisioning domain.
organizationIdstringRequired, indexedOrganization that owns the directory.
entitystringRequiredEntity the mode applies to.
modestringRequired, default create-onlyProjection mode.
createdAtdateRequiredCreation timestamp.
updatedAtdateRequiredLast update timestamp.
lastActorIdstringRequiredActor responsible for the latest mutation.

directorySyncFieldProvenance

Only when fieldProjection.enabled is true. Marks that create-only fields were applied for a provisioning domain + entity + target row.

ColumnTypeConstraintsDescription
provenanceKeystringRequired, unique, hiddenStable key for the provenance row.
provisioningDomainIdstringRequired, indexedSCIM provisioning domain.
entitystringRequiredEntity that was projected.
targetIdstringRequired, indexedPrimary key of the projected row (userId, memberId, …).
appliedAtdateRequiredWhen create-only fields were frozen.
createdAtdateRequiredCreation timestamp.
updatedAtdateRequiredLast update timestamp.

For the full SCIM model and migration guidance, see the SCIM plugin reference and the 1.7 upgrade guide.

Event Tracking

The dash plugin automatically tracks the following events:

User Events

EventTrigger
user_createdNew user registration
profile_updatedUser updates their profile
profile_image_updatedUser changes their avatar
email_verifiedEmail verification completed
email_changedUser email address changed
user_bannedUser is banned
user_unbannedUser is unbanned
user_role_updatedApp-level user role changed
user_deletedUser account deleted
anonymous_user_convertedAnonymous guest linked to a permanent account
two_factor_enabledTwo-factor authentication enabled
two_factor_disabledTwo-factor authentication disabled
two_factor_verifiedTwo-factor verification succeeded

Session Events

EventTrigger
user_signed_inSuccessful sign-in
user_signed_outUser signs out
user_sign_in_failedFailed sign-in attempt
session_createdNew session created
session_revokedSingle session revoked
all_sessions_revokedAll sessions revoked
user_impersonatedAdmin starts impersonating user
user_impersonated_stoppedAdmin stops impersonating

Account Events

EventTrigger
account_linkedSocial account linked
account_unlinkedSocial account unlinked
password_changedPassword updated

Verification Events

EventTrigger
password_reset_requestedPassword reset initiated
password_reset_completedPassword reset finished
email_verification_sentVerification email sent

Organization Events

If you're using the organization plugin, these events are also tracked:

EventTrigger
organization_createdNew organization created
organization_updatedOrganization settings changed
organization_deletedOrganization deleted
organization_member_addedMember added to organization
organization_member_removedMember removed from organization
organization_member_role_updatedMember role changed
organization_member_invitedInvitation sent
organization_member_invite_acceptedInvitation accepted
organization_member_invite_rejectedInvitation rejected
organization_member_invite_canceledInvitation cancelled
organization_team_createdTeam created
organization_team_updatedTeam updated
organization_team_deletedTeam deleted
organization_team_member_addedMember added to team
organization_team_member_removedMember removed from team

Dashboard Endpoints

The dash plugin registers numerous admin endpoints for the dashboard:

Config

EndpointMethodDescription
/dash/configGETAuth config and plugin insights
/dash/validateGETValidate dashboard connectivity

User Management

EndpointMethodDescription
/dash/list-usersGETList users with pagination
/dash/export-usersGETExport users
/dash/userGETGet user details
/dash/user-organizationsGETList organizations for a user
/dash/create-userPOSTCreate a user
/dash/update-userPOSTUpdate user
/dash/delete-userPOSTDelete user
/dash/delete-many-usersPOSTDelete many users
/dash/ban-userPOSTBan user
/dash/ban-many-usersPOSTBan many users
/dash/unban-userPOSTUnban user
/dash/set-user-rolePOSTSet app-level user.role
/dash/set-passwordPOSTSet user password
/dash/unlink-accountPOSTUnlink account
/dash/impersonate-userGETImpersonate user
/dash/send-verification-emailPOSTSend verification email
/dash/send-many-verification-emailsPOSTSend verification emails bulk
/dash/send-reset-password-emailPOSTSend password reset email

Session Management

EndpointMethodDescription
/dash/sessions/revokePOSTRevoke single session
/dash/sessions/revoke-allPOSTRevoke all user sessions
/dash/sessions/revoke-manyPOSTRevoke many sessions

Organization Management

EndpointMethodDescription
/dash/list-organizationsGETList organizations
/dash/export-organizationsGETExport organizations
/dash/organization/optionsGETOrganization plugin options
/dash/organization/:idGETGet organization details
/dash/organization/createPOSTCreate organization
/dash/organization/updatePOSTUpdate organization
/dash/organization/deletePOSTDelete organization
/dash/organization/delete-manyPOSTDelete many organizations
/dash/organization/:id/membersGETList members
/dash/organization/add-memberPOSTAdd member
/dash/organization/remove-memberPOSTRemove member
/dash/organization/update-member-rolePOSTUpdate member role only
/dash/organization/update-memberPOSTUpdate member role and extra fields

Team Management

EndpointMethodDescription
/dash/organization/:id/teamsGETList teams
/dash/organization/create-teamPOSTCreate team
/dash/organization/update-teamPOSTUpdate team
/dash/organization/delete-teamPOSTDelete team
/dash/organization/:orgId/teams/:teamId/membersGETList team members
/dash/organization/add-team-memberPOSTAdd team member
/dash/organization/remove-team-memberPOSTRemove team member

Invitation Management

EndpointMethodDescription
/dash/organization/:id/invitationsGETList invitations
/dash/organization/invite-memberPOSTSend invitation
/dash/organization/cancel-invitationPOSTCancel invitation
/dash/organization/resend-invitationPOSTResend invitation
/dash/organization/check-user-by-emailPOSTCheck user by email
/dash/accept-invitationGETAccept invitation
/dash/complete-invitationPOSTComplete invitation
/dash/complete-invitation-handoffGETInvitation handoff
/dash/complete-invitation-socialGETSocial invitation
/dash/check-user-existsPOSTCheck if user exists

SSO Management

EndpointMethodDescription
/dash/sso/connection-countGETProject-wide SSO connection count
/dash/organization/:id/sso-providersGETList SSO providers
/dash/organization/:id/sso-provider/createPOSTCreate SSO provider
/dash/organization/:id/sso-provider/updatePOSTUpdate SSO provider
/dash/organization/:id/sso-provider/deletePOSTDelete SSO provider
/dash/organization/:id/sso-provider/request-verification-tokenPOSTRequest domain verification token
/dash/organization/:id/sso-provider/verify-domainPOSTVerify domain
/dash/organization/:id/sso-provider/mark-domain-verifiedPOSTMark domain verified

Directory Sync

EndpointMethodDescription
/dash/directory-sync/connection-countGETProject-wide directory connection count
/dash/organization/:id/directoriesGETList directories
/dash/organization/directory/createPOSTCreate legacy directory
/dash/organization/directory/deletePOSTDelete legacy directory
/dash/organization/directory/regenerate-tokenPOSTRegenerate legacy SCIM token
/dash/organization/:id/directoriesPOSTCreate managed directory
/dash/organization/:id/directories/:providerIdGETGet managed directory
/dash/organization/:id/directories/:providerId/credentials/rotatePOSTRotate managed directory credential
/dash/organization/:id/directories/:providerId/credentials/:credentialId/revokePOSTRevoke managed directory credential
/dash/organization/:id/directories/:providerId/eventsGETList managed directory events
/dash/organization/:id/directories/:providerId/decommissionPOSTDecommission managed directory
/dash/organization/:id/directories/:providerId/unpairPOSTUnpair managed directory from SSO
/dash/organization/:id/directory-field-mapping-optionsGETField-mapping catalog
/dash/organization/:id/directories/:providerId/field-mappingsGETGet field mappings and modes
/dash/organization/:id/directories/:providerId/field-mappingsPUTReplace field mappings and modes

Events & Audit Logs

EndpointMethodDescription
/events/listGETCurrent user's events
/events/audit-logsGETCurrent user's audit logs (getAuditLogs)
/events/all-audit-logsGETOrg-admin audit logs (getAllAuditLogs)
/events/typesGETList known event types

Analytics

EndpointMethodDescription
/dash/user-statsGETGet user statistics
/dash/user-graph-dataGETGet graph data
/dash/user-retention-dataGETGet retention data

Two-Factor Management

EndpointMethodDescription
/dash/enable-two-factorPOSTEnable 2FA for user
/dash/complete-two-factor-setupPOSTComplete 2FA setup
/dash/disable-two-factorPOSTDisable 2FA for user
/dash/view-two-factor-totp-uriPOSTGet TOTP URI
/dash/view-backup-codesPOSTView backup codes
/dash/generate-backup-codesPOSTGenerate new backup codes

Client Integration

dashClient()

The client plugin provides access to audit log queries:

import { createAuthClient } from "better-auth/client";
import { dashClient } from "@better-auth/infra/client";

export const authClient = createAuthClient({
  plugins: [dashClient()],
});

Note: For Expo or React Native, import dashClient from @better-auth/infra/native (same API) and pair it with sentinelNativeClient as described in Sentinel — Expo and React Native.

Configuration

dashClient({
  resolveUserId: ({ userId, user, session }) => {
    return userId || user?.id || session?.user?.id;
  },
}),

Get the current user's audit logs

Returns audit events for the current user, or organization-scoped events when you pass organizationId as a member.

Basic query

const session = await authClient.getSession();

const logs = await authClient.dash.getAuditLogs({
  session: session.data,
  limit: 50,
  offset: 0,
});

// Access the data
console.log(logs.data?.events);  // Array of audit log events
console.log(logs.data?.total);   // Total count
console.log(logs.data?.limit);   // Page size
console.log(logs.data?.offset);  // Current offset

See Get current user's audit logs for more information.

Get all audit logs

Returns all audit events for organizations the current user has admin or owner access to. Requires the organization plugin for role checks.

Basic query

const session = await authClient.getSession();

const activity = await authClient.dash.getAllAuditLogs({
  session: session.data,
  limit: 50,
  offset: 0,
});

console.log(activity.data?.events);
console.log(activity.data?.total);

See Get all audit logs for more information.

Best Practices

  1. Always set an API key — without it, the plugin cannot communicate with the infrastructure API.

  2. Use activity tracking wisely — the update interval affects database writes. For high-traffic apps, consider a longer interval.

  3. Monitor audit log retention — different plans have different retention periods. Check your plan limits.

  4. Secure your endpoints — dashboard endpoints require authentication. Make sure your dashboard users have appropriate permissions.