---
title: Instrumentation (Experimental)
description: Distributed tracing for Better Auth
---

<Callout type="warning">
  OpenTelemetry instrumentation is experimental and may change in future releases. The API and span structure are subject to change.
</Callout>

Better Auth is instrumented through [OpenTelemetry](https://opentelemetry.io/) for distributed tracing. Spans are emitted for endpoints, hooks, database operations and the plugin lifecycle.
The goal of this instrumentation is to provide better insights into the behavior of your Better Auth instance, helping you debug issues and optimize performance.

## Setup

Better Auth uses the OpenTelemetry API to create spans. For traces to be collected and exported, at minimum, you must configure a **TracerProvider** and a **SpanExporter** in your application.

For setup instructions, see the official OpenTelemetry documentation:

* **[Node.js getting started](https://opentelemetry.io/docs/languages/js/getting-started/nodejs/)** – Core setup for Node.js apps

## Supported Spans

The following spans are supported by Better Auth:

### Endpoint Spans

| Span Name          | Description                                | When Emitted                                  |
| ------------------ | ------------------------------------------ | --------------------------------------------- |
| `{METHOD} {route}` | Parent span for an auth endpoint           | Every auth API call (e.g. `GET /get-session`) |
| `handler {route}`  | Span around the endpoint handler execution | Inside each endpoint, wrapping the handler    |

**Attributes:**

* `http.route` - The **route template** (low cardinality)
* `better_auth.operation_id` - The operation identifier (e.g `getSession`)

The following global hooks are supported:

| Span Name                      | Description                      | When Emitted                            |
| ------------------------------ | -------------------------------- | --------------------------------------- |
| `hook before {route} {source}` | Before-hook for an auth endpoint | When a `before` hook runs for the route |
| `hook after {route} {source}`  | After-hook for an auth endpoint  | When an `after` hook runs for the route |

* **Attributes:**
  * `better_auth.hook.type` - The hook type (e.g. `before` or `after`)
  * `better_auth.context` - Execution context: `"user"` for global hooks, `"plugin:{pluginId}"` for plugin-registered hooks
  * `http.route` - The **route template** (low cardinality)
  * `better_auth.operation_id` - The operation identifier (e.g `getSession`)

Middlewares are also supported:

| Span Name                       | Description                 | When Emitted                     |
| ------------------------------- | --------------------------- | -------------------------------- |
| `onRequest {pluginId}`          | Plugin `onRequest` hook     | Before request handling          |
| `onResponse {pluginId}`         | Plugin `onResponse` hook    | After response is produced       |
| `middleware {route} {pluginId}` | Plugin middleware execution | When middleware runs for a route |

**Attributes:**

* `better_auth.hook.type` - The hook type: `"onRequest"`, `"onResponse"`, or `"middleware"`
* `better_auth.context` - Execution context: `"plugin:{pluginId}"` (e.g. `plugin:bearer`, `plugin:test-plugin`)
* `http.route` - The **route template** (low cardinality) on middleware spans only
* `http.response.status_code` - The HTTP response status code (onResponse only)

### Database Spans

| Span Name                | Description                   | When Emitted                                   |
| ------------------------ | ----------------------------- | ---------------------------------------------- |
| `db {operation} {model}` | Span for a database operation | All adapter operations (create, findOne, etc.) |

* **Operations:** `create`, `findOne`, `findMany`, `update`, `updateMany`, `delete`, `deleteMany`, `count`
* **Models:** Core models include `user`, `account`, `session`, `verification`, plus plugin-defined models (e.g. `organization`, `rateLimit`).
* **Attributes:**
  * `db.operation.name` - The database operation name (e.g. `create`)
  * `db.collection.name` - The database collection/model name (e.g. `user`)

Additionally, when defined, the following spans are also emitted for database hooks:

| Span Name                       | Description                            | When Emitted                            |
| ------------------------------- | -------------------------------------- | --------------------------------------- |
| `db {operation}.before {model}` | Before-hook for a create/update/delete | When a `databaseHooks` before hook runs |
| `db {operation}.after {model}`  | After-hook for a create/update/delete  | When a `databaseHooks` after hook runs  |

* **Operations:** `create`, `update`, `updateMany`, `delete` (used for both single delete and `deleteMany` calls)
* **Attributes:**
  * `better_auth.hook.type` - The hook type (e.g. `create.before`, `create.after`, `update.before`)
  * `better_auth.context` - Execution context: `"user"` or `"plugin:{pluginId}"`
  * `db.collection.name` - The database collection/model name (e.g. `user`)
