1---
2title: Analytics Engine
3description: Write time-series data points from a Worker with Workers Analytics Engine — declare a dataset, bind it with WriteDataset, and query it later over Cloudflare's SQL API.
4sidebar:
5 order: 24
6---
7
8Workers Analytics Engine is Cloudflare's write-heavy time-series
9store: Workers write unlimited-cardinality data points (events,
10metrics, usage counters) at request time, and you query them later
11with SQL. In alchemy a dataset is a one-line declaration and the
12Worker writes to it through a typed binding.
13
14## Declare a dataset
15
16```typescript
17// src/dataset.ts
18import * as Cloudflare from "alchemy/Cloudflare";
19
20export const Events = Cloudflare.AnalyticsEngine.Dataset("Events", {
21 dataset: "app_events",
22});
23```
24
25A dataset is pure configuration — there is no create/delete API call
26behind it. Cloudflare materializes the dataset the first time a
27Worker writes to it, so declaring one costs nothing. `dataset` is the
28name you'll reference in SQL queries; omit it and the logical ID
29(`"Events"`) is used.
30
31## Write data points from a Worker
32
33Bind the dataset with `WriteDataset` in the Worker's Construction phase and
34provide `WriteDatasetBinding` as a layer:
35
36```typescript
37// src/worker.ts
38import * as Cloudflare from "alchemy/Cloudflare";
39import * as Effect from "effect/Effect";
40import { HttpServerRequest } from "effect/http/HttpServerRequest";
41import * as HttpServerResponse from "effect/http/HttpServerResponse";
42import { Events } from "./dataset.ts";
43
44export default Cloudflare.Worker(
45 "Worker",
46 { main: import.meta.url },
47 Effect.gen(function* () {
48 const analytics = yield* Cloudflare.AnalyticsEngine.WriteDataset(Events);
49
50 return {
51 fetch: Effect.gen(function* () {
52 const request = yield* HttpServerRequest;
53 const url = new URL(request.url, "http://localhost");
54
55 if (url.pathname === "/signup") {
56 yield* analytics.writeDataPoint({
57 indexes: ["account-1"],
58 blobs: ["signup"],
59 doubles: [1],
60 });
61 return yield* HttpServerResponse.json({ ok: true });
62 }
63
64 return HttpServerResponse.text("ok");
65 }).pipe(
66 Effect.catchTag("DatasetError", (error) =>
67 Effect.succeed(
68 HttpServerResponse.text(error.message, { status: 500 }),
69 ),
70 ),
71 ),
72 };
73 }).pipe(Effect.provide(Cloudflare.AnalyticsEngine.WriteDatasetBinding)),
74);
75```
76
77A data point has three optional field groups — `indexes` (the
78sampling key, typically a customer or tenant ID), `blobs` (string
79dimensions to group by), and `doubles` (numeric values to aggregate).
80Writes are fire-and-forget at the edge; a failure surfaces as a typed
81`DatasetError` that Effect keeps in the type system until you handle
82it. The client also exposes `raw` if you need the underlying native
83binding object directly.
84
85## Deploy it
86
87```typescript
88// alchemy.run.ts
89import * as Alchemy from "alchemy";
90import * as Cloudflare from "alchemy/Cloudflare";
91import * as Effect from "effect/Effect";
92import Worker from "./src/worker.ts";
93
94export default Alchemy.Stack(
95 "AnalyticsApp",
96 {
97 providers: Cloudflare.providers(),
98 state: Cloudflare.state(),
99 },
100 Effect.gen(function* () {
101 const worker = yield* Worker;
102 return { url: worker.url.as<string>() };
103 }),
104);
105```
106
107```sh
108bun alchemy deploy
109```
110
111Deploying the Worker attaches the `analytics_engine` binding; hit
112`/signup` and the data point lands in `app_events`.
113
114## Querying your data
115
116The binding is write-only — that mirrors the platform, where Workers
117can only write to Analytics Engine. Reads go through Cloudflare's
118[SQL API](https://developers.cloudflare.com/analytics/analytics-engine/sql-api/),
119an HTTP endpoint you query with an API token:
120
121```sql
122SELECT blob1 AS event, SUM(_sample_interval * double1) AS count
123FROM app_events
124WHERE timestamp > NOW() - INTERVAL '1' DAY
125GROUP BY event
126```
127
128Alchemy doesn't ship a query binding yet, so run queries from
129dashboards, cron jobs, or your backend via plain HTTP.
130
131## Where next
132
133- [Workers](/cloudflare/compute/workers) — the two-phase Worker model the
134 binding lives inside.
135- [Axiom](/axiom) — full observability (logs, traces, dashboards)
136 when you outgrow roll-your-own analytics.
137- [WriteDataset API reference](/providers/cloudflare/analyticsengine/writedataset)