---
title: "Better Stack Adapter"
description: "Send wide events to Better Stack (formerly Logtail) for log management, alerting, and dashboards. Zero-config setup with environment variables."
canonical_url: "https://www.evlog.dev/integrate/adapters/cloud/better-stack"
---
# Better Stack Adapter

> Send wide events to Better Stack (formerly Logtail) for log management, alerting, and dashboards. Zero-config setup with environment variables.

[Better Stack](https://betterstack.com) is a log management platform with search, alerting, and dashboards. The evlog Better Stack adapter sends your wide events to the Better Stack HTTP ingestion API.

<prompt :actions="["copy","cursor","claude"]" description="Add the Better Stack drain adapter" icon="i-simple-icons-betterstack">

Add the Better Stack drain adapter to send evlog wide events to Better Stack.

1. Identify which framework I'm using and follow its evlog integration pattern
2. Install evlog if not already installed
3. Import createBetterStackDrain from 'evlog/better-stack'
4. Wire createBetterStackDrain() into my framework's drain configuration
5. Set BETTER_STACK_API_KEY environment variable
6. Test by triggering a request and checking the Better Stack logs dashboard

Adapter docs: [https://www.evlog.dev/integrate/adapters/cloud/better-stack](https://www.evlog.dev/integrate/adapters/cloud/better-stack)
Framework setup: [https://www.evlog.dev/integrate/frameworks/overview](https://www.evlog.dev/integrate/frameworks/overview)

</prompt>

## Installation

The Better Stack adapter comes bundled with evlog:

```typescript [src/index.ts]
import { createBetterStackDrain } from 'evlog/better-stack'
```

## Quick Start

### 1. Get your source token

1. Create a [Better Stack account](https://betterstack.com)
2. Go to **Telemetry > Sources** and create a new source
3. Copy the **Source Token**

### 2. Set environment variables

```bash [.env]
BETTER_STACK_API_KEY=your-source-token-here
```

<callout color="info" icon="i-lucide-info">

In Better Stack's dashboard this credential is called a **source token**. evlog names the config field `apiKey` for consistency across adapters. The legacy `sourceToken` field still works until the next major release.

</callout>

### 3. Wire the drain to your framework

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog-drain.ts
import { createBetterStackDrain } from 'evlog/better-stack'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createBetterStackDrain())
})
```

```typescript [Nitro]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createBetterStackDrain } from 'evlog/better-stack'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createBetterStackDrain())
})
```

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createBetterStackDrain } from 'evlog/better-stack'

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createBetterStackDrain(),
})
```

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { createBetterStackDrain } from 'evlog/better-stack'

export const { handle, handleError } = createEvlogHooks({ drain: createBetterStackDrain() })
```

```typescript [TanStack Start]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createBetterStackDrain } from 'evlog/better-stack'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createBetterStackDrain())
})
```

```typescript [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import { createBetterStackDrain } from 'evlog/better-stack'

export const middleware: Route.MiddlewareFunction[] = [
  evlog({ drain: createBetterStackDrain() }),
]
```

```typescript [Hono]src/index.ts
import { createBetterStackDrain } from 'evlog/better-stack'

app.use(evlog({ drain: createBetterStackDrain() }))
```

```typescript [Express]src/index.ts
import { createBetterStackDrain } from 'evlog/better-stack'

app.use(evlog({ drain: createBetterStackDrain() }))
```

```typescript [Fastify]src/index.ts
import { createBetterStackDrain } from 'evlog/better-stack'

await app.register(evlog, { drain: createBetterStackDrain() })
```

```typescript [Elysia]src/index.ts
import { createBetterStackDrain } from 'evlog/better-stack'

app.use(evlog({ drain: createBetterStackDrain() }))
```

```typescript [NestJS]src/app.module.ts
import { createBetterStackDrain } from 'evlog/better-stack'

EvlogModule.forRoot({ drain: createBetterStackDrain() })
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createBetterStackDrain } from 'evlog/better-stack'

const handler = withEvlog(new RPCHandler(router), { drain: createBetterStackDrain() })
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createBetterStackDrain } from 'evlog/better-stack'

export default withEvlog(handler, { drain: createBetterStackDrain() })
```

```typescript [Standalone]
import { createBetterStackDrain } from 'evlog/better-stack'

initLogger({ drain: createBetterStackDrain() })
```

</framework-tabs>

That's it! Your logs will now appear in Better Stack.

## Configuration

The adapter reads configuration from multiple sources (highest priority first):

1. **Overrides** passed to `createBetterStackDrain()`
2. **Runtime config** at `runtimeConfig.betterStack` (Nuxt/Nitro only)
3. **Environment variables** (`BETTER_STACK_*`)

### Environment Variables

<table>
<thead>
  <tr>
    <th>
      Variable
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        BETTER_STACK_API_KEY
      </code>
    </td>
    
    <td>
      Better Stack source token (required)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        BETTER_STACK_ENDPOINT
      </code>
    </td>
    
    <td>
      Custom ingestion endpoint
    </td>
  </tr>
</tbody>
</table>

### Runtime Config (Nuxt only)

Configure via `nuxt.config.ts` for type-safe configuration:

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
  runtimeConfig: {
    betterStack: {
      apiKey: '', // Set via BETTER_STACK_API_KEY
    },
  },
})
```

### Override Options

Pass options directly to override any configuration:

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog-drain.ts
import { createBetterStackDrain } from 'evlog/better-stack'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }))
})
```

```typescript [Nitro]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createBetterStackDrain } from 'evlog/better-stack'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }))
})
```

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createBetterStackDrain } from 'evlog/better-stack'

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }),
})
```

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { createBetterStackDrain } from 'evlog/better-stack'

export const { handle, handleError } = createEvlogHooks({
  drain: createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }),
})
```

```typescript [TanStack Start]server/plugins/evlog-drain.ts
import { definePlugin } from 'nitro'
import { createBetterStackDrain } from 'evlog/better-stack'

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }))
})
```

```typescript [React Router]app/root.tsx
import { evlog } from 'evlog/react-router'
import { createBetterStackDrain } from 'evlog/better-stack'

export const middleware: Route.MiddlewareFunction[] = [
  evlog({
      drain: createBetterStackDrain({
        apiKey: 'my-key',
        timeout: 10000,
      }),
    }),
]
```

```typescript [Hono]src/index.ts
import { evlog } from 'evlog/hono'
import { createBetterStackDrain } from 'evlog/better-stack'

app.use(evlog({
  drain: createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }),
}))
```

```typescript [Express]src/index.ts
import { evlog } from 'evlog/express'
import { createBetterStackDrain } from 'evlog/better-stack'

app.use(evlog({
  drain: createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }),
}))
```

```typescript [Fastify]src/index.ts
import { evlog } from 'evlog/fastify'
import { createBetterStackDrain } from 'evlog/better-stack'

await app.register(evlog, {
  drain: createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }),
})
```

```typescript [Elysia]src/index.ts
import { evlog } from 'evlog/elysia'
import { createBetterStackDrain } from 'evlog/better-stack'

app.use(evlog({
  drain: createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }),
}))
```

```typescript [NestJS]src/app.module.ts
import { EvlogModule } from 'evlog/nestjs'
import { createBetterStackDrain } from 'evlog/better-stack'

EvlogModule.forRoot({
  drain: createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }),
})
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createBetterStackDrain } from 'evlog/better-stack'

const handler = withEvlog(new RPCHandler(router), {
  drain: createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }),
})
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createBetterStackDrain } from 'evlog/better-stack'

export default withEvlog(handler, {
  drain: createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }),
})
```

```typescript [Standalone]src/index.ts
import { initLogger } from 'evlog'
import { createBetterStackDrain } from 'evlog/better-stack'

initLogger({
  drain: createBetterStackDrain({
    apiKey: 'my-key',
    timeout: 10000,
  }),
})
```

</framework-tabs>

### Full Configuration Reference

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        apiKey
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Better Stack source token (required)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sourceToken
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      <strong>
        Deprecated.
      </strong>
      
       Use <code>
        apiKey
      </code>
      
       instead
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        endpoint
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        https://in.logs.betterstack.com
      </code>
    </td>
    
    <td>
      Ingestion endpoint
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        timeout
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        5000
      </code>
    </td>
    
    <td>
      Request timeout in milliseconds
    </td>
  </tr>
</tbody>
</table>

## Log Transformation

evlog wide events are transformed using `toBetterStackEvent()`:

- **Timestamp**: `timestamp` is mapped to `dt` (Better Stack's expected ISO-8601 timestamp field)
- **All other fields**: Spread as-is into the event body

Better Stack accepts arbitrary JSON fields, so all your wide event context (level, service, action, user data, etc.) is automatically searchable.

## Querying Logs in Better Stack

Better Stack's log search takes a query over any field:

- **Live tail**: Stream logs in real time
- **Full-text search**: Search across all fields
- **Structured queries**: Filter by `level:error`, `service:my-app`, or any wide event field
- **Dashboards**: Create custom dashboards from your wide event data
- **Alerts**: Set up alerts based on log patterns or thresholds

## Troubleshooting

### Missing apiKey error

```text [Console]
[evlog/better-stack] Missing apiKey. Set BETTER_STACK_API_KEY env var or pass to createBetterStackDrain()
```

Make sure your environment variable is set and the server was restarted after adding it.

### 401 Unauthorized

Your source token may be invalid or revoked. Generate a new source token in **Telemetry > Sources** in the Better Stack dashboard.

### 403 Forbidden

The source may be archived or deleted. Create a new source in Better Stack.

## Direct API Usage

For advanced use cases, you can use the lower-level functions:

```typescript [server/utils/better-stack.ts]
import { sendToBetterStack, sendBatchToBetterStack } from 'evlog/better-stack'

// Send a single event
await sendToBetterStack(event, {
  apiKey: process.env.BETTER_STACK_API_KEY!,
})

// Send multiple events in one request
await sendBatchToBetterStack(events, {
  apiKey: process.env.BETTER_STACK_API_KEY!,
})
```

## Next Steps

- [Axiom Adapter](https://www.evlog.dev/integrate/adapters/cloud/axiom) - Send logs to Axiom for querying and dashboards
- [OTLP Adapter](https://www.evlog.dev/integrate/adapters/hybrid/otlp) - Send logs via OpenTelemetry Protocol
- [Custom Adapters](https://www.evlog.dev/extend/custom-drains) - Build your own adapter

---

- [Better Stack Dashboard](https://logs.betterstack.com)
- [Axiom Adapter](https://www.evlog.dev/integrate/adapters/cloud/axiom)


## Sitemap

See the full [sitemap](https://www.evlog.dev/sitemap.md) for all pages.
