Skip to content

Workers Analytics Engine -- Custom Event Tracking

DodaTech 6 min read

In this tutorial, you'll learn about Workers Analytics Engine. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Cloudflare Workers Analytics Engine is a high-throughput, SQL-queryable time-series database integrated into the Workers runtime, enabling custom event tracking, usage metering, and real-time analytics without managing external infrastructure.

Why Analytics Engine Matters

Every application needs analytics -- page views, API usage, error rates, user behavior. Traditional approaches send events to external services (Google Analytics, Mixpanel, Datadog), adding latency, cost, and dependency on third parties. Analytics Engine eliminates these by providing built-in event ingestion directly from your Workers, with data stored in Cloudflare's edge network. Events are written with zero additional network hops and are queryable within seconds via a SQL API. Unlike Cloudflare's Logpush which streams raw request logs, Analytics Engine is designed for custom application events with user-defined schemas. This enables product analytics, billing metering, and performance monitoring that would otherwise require a dedicated data pipeline built with REST APIs and a separate database.

Real-world use: A SaaS platform writes an analytics event for every API call, recording endpoint name, response time, user tier, and status code. The platform queries the Analytics Engine dashboard to show real-time usage metrics per customer, driving their billing system.

Analytics Engine Architecture

flowchart LR
    W[Worker Code] --> AE[Analytics Engine API]
    AE --> B[Batching Layer]
    B --> S[(Time-Series Storage)]
    S --> SQL[SQL Query API]
    SQL --> D[Dashboard]
    SQL --> A[Automated Alerts]

    subgraph Event_Ingestion
        E1[Event 1] --> B
        E2[Event 2] --> B
        E3[Event 100] --> B
    end

    style AE fill:#f90,color:#fff
    style S fill:#3498db,color:#fff
    style SQL fill:#2ecc71,color:#fff

Events are written from Workers using a simple method call. The runtime batches events for efficient ingestion. The SQL query API provides real-time access to the stored data with standard aggregation functions.

Writing Analytics Events

// wrangler.toml
// [[analytics_engine_datasets]]
// binding = "ANALYTICS"
// dataset = "api_events"

export default {
  async fetch(request, env) {
    const start = Date.now();
    const url = new URL(request.url);
    const method = request.method;

    const response = await fetch('https://api.example.com' + url.pathname, {
      method,
      headers: request.headers
    });

    const duration = Date.now() - start;

    // Write analytics event
    env.ANALYTICS.writeDataPoint({
      blobs: [url.pathname, method, String(response.status)],
      doubles: [duration, response.headers.get('content-length') || 0],
      indexes: [url.pathname]
    });

    return response;
  }
};

// Expected behavior:
// Every request writes one analytics data point
// Events are visible in SQL queries within ~10 seconds
// No external service calls -- zero added latency

The writeDataPoint method accepts three arrays: blobs for string data (up to 20 blobs, 512 bytes each), doubles for numeric data (up to 40 doubles), and indexes for query-filtered strings (up to 2 indexes). The dataset is defined in wrangler.toml with a binding name.

Querying Events with SQL

-- Query: Average response time by endpoint, last hour
SELECT
  blob1 AS endpoint,
  COUNT(*) AS requests,
  AVG(double1) AS avg_duration_ms,
  MAX(double1) AS max_duration_ms
FROM api_events
WHERE timestamp >= NOW() - INTERVAL '1' HOUR
  AND timestamp < NOW()
GROUP BY blob1
ORDER BY requests DESC
LIMIT 10;

-- Expected output:
-- endpoint        | requests | avg_duration_ms | max_duration_ms
-- /api/users      | 1850     | 45.2            | 320
-- /api/orders     | 920      | 120.7           | 890
-- /api/products   | 780      | 32.1            | 150
// Query from another Worker using the SQL API
export default {
  async fetch(request, env) {
    const query = `
      SELECT
        blob1 AS endpoint,
        COUNT(*) AS total,
        AVG(double1) AS avg_latency
      FROM api_events
      WHERE timestamp >= NOW() - INTERVAL '1' HOUR
      GROUP BY blob1
      ORDER BY total DESC
    `;

    const response = await env.ANALYTICS.query(query);

    return new Response(JSON.stringify(await response.json()), {
      headers: { 'Content-Type': 'application/json' }
    });
  }
};

// Expected output:
// {"meta": [{"name": "endpoint", "type": "text"}, ...],
//  "data": [
//    {"endpoint": "/api/users", "total": 1850, "avg_latency": 45.2},
//    ...
//  ]}

The env.ANALYTICS.query() method executes SQL against your dataset and returns results as a JSON object. The SQL dialect supports standard aggregation, filtering, grouping, and time-range queries. Results typically return in under 100 milliseconds.

Usage Metering for Billing

export default {
  async fetch(request, env) {
    const apiKey = request.headers.get('X-API-Key');
    const { results } = await env.DB.prepare(
      'SELECT customer_id, tier FROM customers WHERE api_key = ?'
    ).bind(apiKey).first();

    if (!results) {
      return new Response('Unauthorized', { status: 401 });
    }

    const { customer_id, tier } = results;
    const start = Date.now();

    const response = await fetch('https://api.example.com/data');
    const duration = Date.now() - start;

    // Track usage per customer
    env.ANALYTICS.writeDataPoint({
      blobs: [customer_id, tier, 'data_fetch'],
      doubles: [duration, 1],
      indexes: [customer_id]
    });

    return response;
  }
};

// Billing query: Count requests per customer this month
const billingQuery = `
  SELECT
    blob1 AS customer_id,
    blob2 AS tier,
    SUM(double2) AS request_count
  FROM api_events
  WHERE timestamp >= DATE_TRUNC('month', NOW())
    AND blob1 = 'cust_12345'
  GROUP BY blob1, blob2
`;

// Expected output:
// customer_id | tier   | request_count
// cust_12345  | pro    | 45210

Analytics Engine excels at usage metering because events are written synchronously from the request path with no external dependencies. The SQL API makes it straightforward to generate per-customer usage reports for billing or rate limit enforcement.

Common Errors

Error Cause Fix
Analytics Engine binding not found Dataset not defined in wrangler.toml Add [[analytics_engine_datasets]] with the correct binding and dataset name
Blob value exceeds maximum length String blob longer than 512 bytes Truncate long strings or split into multiple blobs
Double array exceeds maximum size More than 40 double values provided Reduce the number of numeric fields per event
Index array exceeds maximum size More than 2 index values provided Use only the most important filter fields as indexes
Query timeout SQL query scans too much data Add a time range filter with WHERE timestamp >= NOW() - INTERVAL '1' DAY'

Practice Questions

  1. What three types of arrays does writeDataPoint accept?
  2. How do you define an Analytics Engine dataset in wrangler.toml?
  3. What SQL function can you use to filter events to a specific time range?

FAQ

How long does Analytics Engine retain data?

Data retention depends on your Cloudflare plan. Free plans retain data for 31 days. Paid plans offer up to 12 months retention. You can query data within the retention window using the SQL API. Export important data to R2 or external storage for long-term archival.

What is the throughput limit for Analytics Engine?

Analytics Engine is designed for high-throughput event ingestion with no hard per-Worker limit. The free plan includes a daily event quota. Paid plans have higher limits based on your subscription tier. Each event is batched and ingested efficiently without blocking the request path.

{{< faq "Can I query Analytics Engine from outside Cloudflare?">}} Yes. Analytics Engine data can be queried via the Cloudflare API using the account-level SQL query endpoint. This allows you to build external dashboards, export data to BI tools, or integrate with custom reporting pipelines.{{< /faq >}}

Summary

Workers Analytics Engine provides built-in, SQL-queryable event tracking directly from your Workers. Write data points with strings, numbers, and indexes using writeDataPoint(), then query with standard SQL to build dashboards, metering reports, and performance monitoring. Events are ingested with zero added latency and are queryable within seconds. Analytics Engine eliminates the need for external analytics services and Data Pipelines. DodaTech uses Analytics Engine to track API usage across its product suite, powering usage-based billing for DodaZIP and Durga Antivirus Pro.

This guide is brought to you by the developers of Cloudflare, SQL queries, and Durga Antivirus Pro at DodaTech.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro