Loading...
Loading...
Write and query high-cardinality event data at scale with SQL. Load when tracking user events, billing metrics, per-tenant analytics, A/B testing, API usage, or custom telemetry. Use writeDataPoint for non-blocking writes and SQL API for aggregations.
npx skill4agent add null-shot/cloudflare-skills analytics-enginewrangler analytics-engine create my-datasetwrangler.jsonc{
"analytics_engine_datasets": [
{
"binding": "USER_EVENTS",
"dataset": "my-dataset"
}
]
}| Use Case | Why Analytics Engine |
|---|---|
| User behavior tracking | High-cardinality data (userId, sessionId, etc.) |
| Billing/usage metrics | Per-tenant aggregation with doubles |
| Custom telemetry | Non-blocking writes, queryable with SQL |
| A/B test metrics | Index by experiment ID, query results |
| API usage tracking | Count requests per customer/endpoint |
| Operation | API | Notes |
|---|---|---|
| Write event | | Non-blocking, do NOT await |
| Metrics | | Up to 20 numeric values |
| Labels | | Up to 20 text values |
| Grouping | | 1 index per datapoint (max 96 bytes) |
| Query data | SQL API via REST | GraphQL also available |
| Field Type | Purpose | Limit | Example |
|---|---|---|---|
| doubles | Numeric metrics (counters, gauges, latency) | 20 per datapoint | |
| blobs | Text labels (URLs, names, IDs) | 20 per datapoint | |
| indexes | Grouping key (userId, tenantId, etc.) | 1 per datapoint | |
interface Env {
USER_EVENTS: AnalyticsEngineDataset;
}
export default {
async fetch(req: Request, env: Env): Promise<Response> {
let url = new URL(req.url);
let path = url.pathname;
let userId = url.searchParams.get("userId");
// Write a datapoint for this visit, associating the data with
// the userId as our Analytics Engine 'index'
env.USER_EVENTS.writeDataPoint({
// Write metrics data: counters, gauges or latency statistics
doubles: [],
// Write text labels - URLs, app names, event_names, etc
blobs: [path],
// Provide an index that groups your data correctly.
indexes: [userId],
});
return Response.json({
hello: "world",
});
},
};interface Env {
API_METRICS: AnalyticsEngineDataset;
}
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const start = Date.now();
const url = new URL(req.url);
const apiKey = req.headers.get("x-api-key") || "anonymous";
const endpoint = url.pathname;
try {
// Handle API request...
const response = await handleApiRequest(req);
const duration = Date.now() - start;
// Track successful request
env.API_METRICS.writeDataPoint({
doubles: [duration, response.headers.get("content-length") || 0],
blobs: [endpoint, "success", response.status.toString()],
indexes: [apiKey],
});
return response;
} catch (error) {
const duration = Date.now() - start;
// Track failed request
env.API_METRICS.writeDataPoint({
doubles: [duration, 0],
blobs: [endpoint, "error", error.message],
indexes: [apiKey],
});
return new Response("Error", { status: 500 });
}
},
};awaitwriteDataPoint()// ❌ WRONG - Do not await
await env.USER_EVENTS.writeDataPoint({ ... });
// ✅ CORRECT - Fire and forget
env.USER_EVENTS.writeDataPoint({ ... });https://api.cloudflare.com/client/v4/accounts/{account_id}/analytics_engine/sqlSELECT
timestamp,
blob1 AS path,
index1 AS userId
FROM USER_EVENTS
WHERE timestamp > NOW() - INTERVAL '1' DAY
ORDER BY timestamp DESC
LIMIT 100SELECT
index1 AS apiKey,
COUNT(*) AS request_count,
AVG(double1) AS avg_duration_ms,
SUM(double2) AS total_bytes
FROM API_METRICS
WHERE timestamp > NOW() - INTERVAL '7' DAY
GROUP BY apiKey
ORDER BY request_count DESCcurl "https://api.cloudflare.com/client/v4/accounts/{account_id}/analytics_engine/sql" \
--header "Authorization: Bearer <API_TOKEN>" \
--data "SHOW TABLES"double1double2double20blob1blob2blob20index1index2index20ASSELECT
double1 AS response_time,
blob1 AS endpoint,
index1 AS user_id
FROM my_dataset{
"name": "analytics-engine-example",
"main": "src/index.ts",
"compatibility_date": "2025-02-11",
"analytics_engine_datasets": [
{
"binding": "USER_EVENTS",
"dataset": "user-events"
},
{
"binding": "API_METRICS",
"dataset": "api-metrics"
}
]
}interface Env {
// Analytics Engine dataset binding
USER_EVENTS: AnalyticsEngineDataset;
}
// Datapoint structure
interface AnalyticsEngineDataPoint {
doubles?: number[]; // Up to 20 numeric values
blobs?: string[]; // Up to 20 text values
indexes?: string[]; // Up to 20 grouping keys
}writeDataPoint()env.SESSIONS.writeDataPoint({
doubles: [sessionDuration, pageViews, eventsCount],
blobs: [browser, country, deviceType],
indexes: [userId, sessionId],
});env.ERRORS.writeDataPoint({
doubles: [1], // Error count
blobs: [errorType, errorMessage.slice(0, 256), endpoint],
indexes: [userId, appVersion],
});env.REVENUE.writeDataPoint({
doubles: [amountCents, taxCents, discountCents],
blobs: [productId, currency, paymentMethod],
indexes: [customerId, merchantId],
});// Before: D1
await env.DB.prepare("INSERT INTO events (userId, event) VALUES (?, ?)")
.bind(userId, event)
.run();
// After: Analytics Engine
env.EVENTS.writeDataPoint({
blobs: [event],
indexes: [userId],
}); // Non-blocking, no await