# Cloudflare Analytics Engine: store and query metrics

> How to write custom events from a Worker with Analytics Engine and query them later with SQL. Cheap, high-volume time-series data.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-06-29 | Topics: [Cloudflare](https://flaviocopes.com/tags/cloudflare/) | Canonical: https://flaviocopes.com/cloudflare-analytics-engine/

Say you want to track every time someone uses a feature, then chart it later. You could write each event into your database, but that gets expensive and slow when there are millions of them.

**Analytics Engine** is built for exactly this. You write huge numbers of events, cheaply, and query them later with SQL. It's a time-series store, the kind of thing you'd use for usage metrics and dashboards.

## Set up a dataset

Your events go into a **dataset**. You don't create it ahead of time, you just name it in `wrangler.jsonc` and it appears:

```jsonc
{
  "analytics_engine_datasets": [
    { "binding": "ANALYTICS", "dataset": "my_app_events" }
  ]
}
```

Now `env.ANALYTICS` is ready in your Worker.

## Write an event

You record an event with `writeDataPoint`. It takes three kinds of fields:

```js
export default {
  async fetch(request, env) {
    env.ANALYTICS.writeDataPoint({
      blobs: ['feature-used', 'pricing-page'],
      doubles: [1],
      indexes: ['user-123'],
    })

    return new Response('ok')
  },
}
```

Let me explain the three fields, because the names aren't obvious:

- **blobs** are strings. The text you want to store: an event name, a page, a country.
- **doubles** are numbers. Things you'll add up or average, like a count or a duration.
- **indexes** is the value you'll group and filter by most. Pick one, like a user id or tenant id.

Notice there's no `await`. Writing is fire-and-forget, so it adds basically nothing to your response time. That's the point.

## Query with SQL

Later, you query the data with SQL through Cloudflare's API. You send a query to an HTTP endpoint with an API token:

```bash
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/analytics_engine/sql" \
  -H "Authorization: Bearer $API_TOKEN" \
  -d "SELECT blob1 AS event, count() AS total
      FROM my_app_events
      WHERE timestamp > now() - INTERVAL '1' DAY
      GROUP BY event"
```

A couple of things to notice. Your blobs come back as `blob1`, `blob2`, and so on, in the order you wrote them. Your doubles are `double1`, `double2`. And there's a `timestamp` column for free on every event.

So this query counts each event type over the last day. Perfect for a dashboard.

## A real use: per-tenant usage

In a SaaS app, I track usage per customer. The tenant id goes in `indexes`, the event in `blobs`, and a count in `doubles`:

```js
env.ANALYTICS.writeDataPoint({
  blobs: ['form-submission'],
  doubles: [1],
  indexes: [tenantId],
})
```

Then at the end of the month, one SQL query tells me how much each tenant used. No giant table in my main database, no slow queries.

## When to use it

Analytics Engine is for events you have a *lot* of and want to analyze in aggregate: page views, feature usage, API calls, durations.

It's not for data you need to read back exactly, one record at a time. That's still D1 or KV. Analytics Engine is for "how many, how often, grouped by what."

It's cheap, it's fast to write, and the SQL makes it easy to slice. The full reference is in the [Analytics Engine docs](https://developers.cloudflare.com/analytics/analytics-engine/).
