---
title: "Axiom Adapter"
description: "Send wide events to Axiom for powerful querying, dashboards, and alerting. Zero-config setup with environment variables and automatic batching."
canonical_url: "https://www.evlog.dev/integrate/adapters/cloud/axiom"
---
# Axiom Adapter

> Send wide events to Axiom for powerful querying, dashboards, and alerting. Zero-config setup with environment variables and automatic batching.

[Axiom](https://axiom.co) is a cloud-native logging platform that indexes every field of an event, so any of them is queryable without a schema. The evlog Axiom adapter sends your wide events directly to Axiom datasets.

<prompt :actions="["copy","cursor","claude"]" description="Add the Axiom drain adapter" icon="i-custom-axiom">

Add the Axiom drain adapter to send evlog wide events to Axiom.

1. Identify which framework I'm using and follow its evlog integration pattern
2. Install evlog if not already installed
3. Import createAxiomDrain from 'evlog/axiom'
4. Wire createAxiomDrain() into my framework's drain configuration
5. Set AXIOM_API_KEY and AXIOM_DATASET environment variables in .env
6. Test by triggering a request and checking the Axiom dataset

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

</prompt>

## Installation

The Axiom adapter comes bundled with evlog:

```typescript [src/index.ts]
import { createAxiomDrain } from 'evlog/axiom'
```

## Quick Start

### 1. Get your Axiom credentials

1. Create an [Axiom account](https://app.axiom.co)
2. Create a dataset for your logs
3. Generate an API token with ingest permissions

### 2. Set environment variables

```bash [.env]
AXIOM_API_KEY=xaat-your-token-here
AXIOM_DATASET=your-dataset-name
```

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

In Axiom's dashboard this credential is called an **API token** (`xaat-...`). evlog names the config field `apiKey` for consistency across adapters. Legacy `token` / `AXIOM_TOKEN` still work until the next major release.

</callout>

### 3. Wire the drain to your framework

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog-drain.ts
import { createAxiomDrain } from 'evlog/axiom'

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

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

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

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createAxiomDrain } from 'evlog/axiom'

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

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { createAxiomDrain } from 'evlog/axiom'

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

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

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

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

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

```typescript [Hono]src/index.ts
import { createAxiomDrain } from 'evlog/axiom'

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

```typescript [Express]src/index.ts
import { createAxiomDrain } from 'evlog/axiom'

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

```typescript [Fastify]src/index.ts
import { createAxiomDrain } from 'evlog/axiom'

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

```typescript [Elysia]src/index.ts
import { createAxiomDrain } from 'evlog/axiom'

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

```typescript [NestJS]src/app.module.ts
import { createAxiomDrain } from 'evlog/axiom'

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

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createAxiomDrain } from 'evlog/axiom'

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

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createAxiomDrain } from 'evlog/axiom'

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

```typescript [Standalone]
import { createAxiomDrain } from 'evlog/axiom'

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

</framework-tabs>

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

## Configuration

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

1. **Overrides** passed to `createAxiomDrain()`
2. **Runtime config** at `runtimeConfig.axiom` (Nuxt/Nitro only)
3. **Environment variables** (`AXIOM_*`)

### Environment Variables

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

<tbody>
  <tr>
    <td>
      <code>
        AXIOM_API_KEY
      </code>
    </td>
    
    <td>
      Axiom API token with ingest permissions
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        AXIOM_DATASET
      </code>
    </td>
    
    <td>
      Dataset name to ingest logs into
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        AXIOM_ORG_ID
      </code>
    </td>
    
    <td>
      Organization ID (required for Personal Access Tokens)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        AXIOM_EDGE_URL
      </code>
    </td>
    
    <td>
      Edge base URL for ingest/query (for edge deployments)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        AXIOM_URL
      </code>
    </td>
    
    <td>
      API base URL (legacy/default ingest 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: {
    axiom: {
      apiKey: '', // Set via AXIOM_API_KEY
      dataset: '', // Set via AXIOM_DATASET
    },
  },
})
```

### Override Options

Pass options directly to override any configuration:

<framework-tabs>

```typescript [Nuxt]server/plugins/evlog-drain.ts
import { createAxiomDrain } from 'evlog/axiom'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }))
})
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }))
})
```

```typescript [Next.js]lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createAxiomDrain } from 'evlog/axiom'

export const { withEvlog, useLogger, log, createError } = createEvlog({
  service: 'my-app',
  drain: createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }),
})
```

```typescript [SvelteKit]src/hooks.server.ts
import { createEvlogHooks } from 'evlog/sveltekit'
import { createAxiomDrain } from 'evlog/axiom'

export const { handle, handleError } = createEvlogHooks({
  drain: createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }),
})
```

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

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }))
})
```

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

export const middleware: Route.MiddlewareFunction[] = [
  evlog({
      drain: createAxiomDrain({
        dataset: 'production-logs',
        timeout: 10000,
      }),
    }),
]
```

```typescript [Hono]src/index.ts
import { evlog } from 'evlog/hono'
import { createAxiomDrain } from 'evlog/axiom'

app.use(evlog({
  drain: createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }),
}))
```

```typescript [Express]src/index.ts
import { evlog } from 'evlog/express'
import { createAxiomDrain } from 'evlog/axiom'

app.use(evlog({
  drain: createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }),
}))
```

```typescript [Fastify]src/index.ts
import { evlog } from 'evlog/fastify'
import { createAxiomDrain } from 'evlog/axiom'

await app.register(evlog, {
  drain: createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }),
})
```

```typescript [Elysia]src/index.ts
import { evlog } from 'evlog/elysia'
import { createAxiomDrain } from 'evlog/axiom'

app.use(evlog({
  drain: createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }),
}))
```

```typescript [NestJS]src/app.module.ts
import { EvlogModule } from 'evlog/nestjs'
import { createAxiomDrain } from 'evlog/axiom'

EvlogModule.forRoot({
  drain: createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }),
})
```

```typescript [oRPC]server/orpc.ts
import { withEvlog } from 'evlog/orpc'
import { createAxiomDrain } from 'evlog/axiom'

const handler = withEvlog(new RPCHandler(router), {
  drain: createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }),
})
```

```typescript [Cloudflare Workers]src/worker.ts
import { withEvlog } from 'evlog/workers'
import { createAxiomDrain } from 'evlog/axiom'

export default withEvlog(handler, {
  drain: createAxiomDrain({
    dataset: 'production-logs',
    timeout: 10000,
  }),
})
```

```typescript [Standalone]src/index.ts
import { initLogger } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'

initLogger({
  drain: createAxiomDrain({
    dataset: 'production-logs',
    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>
      Axiom API token (required)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        token
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      <strong>
        Deprecated.
      </strong>
      
       Use <code>
        apiKey
      </code>
      
       instead
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dataset
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Dataset name (required)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        orgId
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Organization ID (for PAT tokens)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        edgeUrl
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Edge URL for ingest. Uses <code>
        /v1/ingest/{dataset}
      </code>
      
       when no path is provided; custom paths are used as-is (trailing slash trimmed). Mutually exclusive with <code>
        baseUrl
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        baseUrl
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        https://api.axiom.co
      </code>
    </td>
    
    <td>
      API base URL (<code>
        /v1/datasets/{dataset}/ingest
      </code>
      
      ), mutually exclusive with <code>
        edgeUrl
      </code>
    </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>
  
  <tr>
    <td>
      <code>
        retries
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      Number of retry attempts on transient failures
    </td>
  </tr>
</tbody>
</table>

### Timestamps

Axiom's event timestamp field is `_time`. The adapter sends the event's own `timestamp` field as the timestamp via the `timestamp-field` ingest parameter, so events keep their original time even when batched or retried. Time-based queries and monitors should bin on `_time`.

## Querying Logs in Axiom

evlog sends structured wide events that are perfect for Axiom's APL query language:

```apl [Axiom APL queries]
// Find slow requests
['your-dataset']
| where durationMs > 1000
| project _time, path, durationMs, status

// Error rate by endpoint
['your-dataset']
| where level == "error"
| summarize count() by path
| order by count_ desc

// Request volume over time
['your-dataset']
| summarize count() by bin(_time, 1h)
| render timechart
```

## Troubleshooting

### Missing dataset or apiKey error

```text [Console]
[evlog/axiom] Missing dataset or apiKey. Set AXIOM_API_KEY/AXIOM_DATASET env vars or pass to createAxiomDrain()
```

Make sure your environment variables are set and the server was restarted after adding them.

### 401 Unauthorized

Your token may be invalid or expired. Generate a new token in the Axiom dashboard with **Ingest** permissions.

### 403 Forbidden with PAT tokens

Personal Access Tokens require an organization ID:

```bash [.env]
AXIOM_ORG_ID=your-org-id
```

## Direct API Usage

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

```typescript [server/utils/axiom.ts]
import { sendToAxiom, sendBatchToAxiom } from 'evlog/axiom'

// Send a single event
await sendToAxiom(event, {
  apiKey: 'xaat-xxx',
  dataset: 'logs',
})

// Send multiple events in one request
await sendBatchToAxiom(events, {
  apiKey: 'xaat-xxx',
  dataset: 'logs',
})
```

## Next Steps

- [OTLP Adapter](https://www.evlog.dev/integrate/adapters/hybrid/otlp) - Send logs via OpenTelemetry Protocol
- [PostHog Adapter](https://www.evlog.dev/integrate/adapters/cloud/posthog) - Send logs to PostHog
- [Custom Adapters](https://www.evlog.dev/extend/custom-drains) - Build your own adapter
- [Best Practices](https://www.evlog.dev/reference/best-practices) - Security and production tips

---

- [Axiom Dashboard](https://app.axiom.co)
- [OTLP Adapter](https://www.evlog.dev/integrate/adapters/hybrid/otlp)


## Sitemap

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