Silgi

Better Auth

Trace Better Auth operations in silgi analytics — login flows, OAuth, session management with structured attributes.

The Better Auth integration traces every auth operation — sign-in, sign-up, OAuth flows, session management — into silgi analytics. Each operation appears as a span with user.id, session.id, auth.operation, and success/failure status. Two integration points cover both HTTP requests and programmatic API calls.

Setup

bash pnpm add silgi better-auth
bash npm install silgi better-auth
bash bun add silgi better-auth

There are two ways to trace auth operations:

  1. tracing() plugin — traces HTTP-level auth requests (sign-in forms, OAuth callbacks)
  2. instrumentBetterAuth() — traces programmatic auth.api.* calls (server-side session fetches, background jobs)

Use one or both depending on your setup.

Plugin setup

Add tracing() to your Better Auth plugins. Pass createAuthMiddleware from better-auth/api so the plugin can wrap the after-hook handler:

auth.ts
import {  } from 'better-auth'
import {  } from 'better-auth/api'
import {  } from 'silgi/better-auth'

export const  = ({
  : { : process.env.DATABASE_URL! },
  : [({  })],
})

Handler setup

In your silgi handler, call setRequestContext(request, ctx) before passing the request to Better Auth. This bridges the silgi request context to the auth plugin via a GC-friendly WeakMap — no mutation of the Request object.

server.ts
import {  } from 'silgi'
import {  } from 'silgi/better-auth'
import {  } from './auth'

const  = ()

const  = .(async ({ ,  }) => {
  (, )
  return .handler()
})

const  = .({
  : { :  },
})

export default { : .() }

setRequestContext(request, ctx) must run before auth.handler(request). Without it the plugin has no access to the request trace and spans are silently skipped.

The legacy (request as any).__silgiCtx = ctx assignment still works as a fallback for existing code, but new code should prefer setRequestContext — it avoids mutating the Request and releases the entry automatically when the Request is GC'd.

What gets captured

Each auth operation produces a span with these attributes:

AttributeTypeDescription
auth.operationstringOperation name (signin, signup, signout, get_session, etc.)
auth.methodstringAuth method (email, oauth)
auth.providerstringOAuth provider name (google, github, etc.)
auth.successbooleanWhether the operation succeeded
user.idstringAuthenticated user's ID
user.emailstringAuthenticated user's email
session.idstringSession ID

Operation mapping

The plugin maps Better Auth URL paths to structured span names:

PathSpan nameOperation
/sign-up/emailauth.signup.emailsignup
/sign-in/emailauth.signin.emailsignin
/sign-outauth.signoutsignout
/get-sessionauth.get_sessionget_session
/update-userauth.update_userupdate_user
/delete-userauth.delete_userdelete_user
/change-passwordauth.change_passwordchange_password
/change-emailauth.change_emailchange_email
/verify-emailauth.verify_emailverify_email
/forget-passwordauth.forgot_passwordforgot_password
/reset-passwordauth.reset_passwordreset_password
/list-sessionsauth.list_sessionslist_sessions
/revoke-sessionauth.revoke_sessionrevoke_session
/sign-in/{provider}auth.oauth.initiate.{provider}oauth_initiate
/callback/{provider}auth.oauth.callback.{provider}oauth_callback

Unrecognized paths fall back to auth.{last_segment} with operation unknown.

Programmatic API tracing

For server-side calls to auth.api.* (session validation, background user management), use instrumentBetterAuth with withCtx:

auth.ts
import {  } from 'better-auth'
import {  } from 'silgi/better-auth'

export const  = (
  ({
    : { : process.env.DATABASE_URL! },
  }),
)

Then wrap API calls in withCtx:

server.ts
import {  } from 'silgi'
import {  } from 'silgi/better-auth'
import {  } from './auth'

const  = ()

const  = .(async ({ ,  }) => {
  return (, () => .api.getSession({ : .headers }))
})

Programmatic spans are named auth.api.{operation} (e.g. auth.api.get_session, auth.api.signin). All known API methods are mapped to their operation names automatically. Unknown methods are also traced using a camelCase-to-snake_case conversion of the method name.

Configuration

Options for the tracing() plugin:

NameTypeDefaultDescription
captureInputbooleantrueCapture request body as span input
captureOutputbooleantrueCapture response data as span output
createAuthMiddlewarefunctionundefinedPass createAuthMiddleware from better-auth/api to wrap the after-hook handler

On this page