All skills
cloudflare avatar

/basin

@41e0d19 official
by cloudflarecloudflare/skills3k stars
298

Build and troubleshoot Cloudflare Basin analytics workflows with Basin Pipelines, Basin Catalog, and Basin SQL. Use for streaming data into R2 Iceberg tables, managing catalogs, or querying those tables; also use for requests using the former Data Platform, Pipelines, R2 Data Catalog, or R2 SQL names.

Use this Skill: https://skilld.dev/gh/cloudflare/skills/basin

This session only. Nothing lands on disk.

referencessqlpatterns.md

≈1.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Basin SQL Patterns

Code templates for CLI, REST, and Worker access. For performance/partitioning best practices, pull https://developers.cloudflare.com/basin-sql/reference/limitations-best-practices/index.md.

Wrangler CLI

export WRANGLER_BASIN_SQL_AUTH_TOKEN=$API_TOKEN

npx wrangler basin sql query "${ACCOUNT_ID}_my-bucket" "
  SELECT category, COUNT(*) AS cnt, round(AVG(amount), 2) AS avg_amount
  FROM analytics.events
  WHERE __ingest_ts >= '2026-01-01T00:00:00Z'
  GROUP BY category ORDER BY cnt DESC LIMIT 100"

REST API (Python)

import requests

API = f"https://api.sql.cloudflarestorage.com/api/v1/accounts/{ACCOUNT_ID}/basin-sql/query/{BUCKET}"
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}

def r2sql(query):
    body = requests.post(API, headers=HEADERS, json={"query": query}, timeout=180).json()
    if body["success"]:
        return body["result"]["rows"], body["result"]["metrics"]
    raise RuntimeError(body["errors"])

rows, metrics = r2sql("SELECT category, COUNT(*) AS cnt FROM analytics.events GROUP BY category LIMIT 10")

REST API (curl)

curl -X POST \
  "https://api.sql.cloudflarestorage.com/api/v1/accounts/$ACCOUNT_ID/basin-sql/query/$BUCKET" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"query": "SELECT COUNT(*) AS total FROM analytics.events"}'

Dashboard Worker

No Basin SQL binding exists — query the REST endpoint via fetch().

interface Env { ACCOUNT_ID: string; BUCKET: string; R2_SQL_TOKEN: string; }

async function queryR2SQL(env: Env, query: string) {
  const url = `https://api.sql.cloudflarestorage.com/api/v1/accounts/${env.ACCOUNT_ID}/basin-sql/query/${env.BUCKET}`;
  const resp = await fetch(url, {
    method: "POST",
    headers: { Authorization: `Bearer ${env.R2_SQL_TOKEN}`, "Content-Type": "application/json" },
    body: JSON.stringify({ query }),
  });
  if (!resp.ok) throw new Error(`Basin SQL ${resp.status}: ${await resp.text()}`);
  return (await resp.json() as any).result;
}

export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    if (new URL(req.url).pathname === "/api/analytics") {
      const result = await queryR2SQL(env, `
        SELECT category, COUNT(*) AS cnt FROM analytics.events
        GROUP BY category ORDER BY cnt DESC LIMIT 10`);
      return Response.json(result.rows);
    }
    return new Response("Not found", { status: 404 });
  },
};
npx wrangler secret put R2_SQL_TOKEN

Example Queries

-- Error rate by endpoint
SELECT path, COUNT(*) AS total, SUM(CASE WHEN status >= 400 THEN 1 ELSE 0 END) AS errors
FROM logs.http_requests WHERE __ingest_ts >= '2026-01-01T00:00:00Z'
GROUP BY path ORDER BY errors DESC LIMIT 20;

-- Top-3 slowest requests per method (window + QUALIFY)
SELECT method, path, response_time_ms FROM logs.http_requests
QUALIFY ROW_NUMBER() OVER (PARTITION BY method ORDER BY response_time_ms DESC) <= 3;

-- Cross-table analytics with approx distinct
SELECT z.domain, COUNT(*) AS requests, approx_distinct(h.client_ip) AS uniques
FROM ns.zones z INNER JOIN ns.http_requests h ON z.zone_id = h.zone_id
WHERE h.__ingest_ts >= '2026-06-01T00:00:00Z'
GROUP BY z.domain ORDER BY requests DESC LIMIT 25;

Cursor-Based Pagination

Paginate on a sortable (ideally partition) column rather than OFFSET:

SELECT * FROM logs.requests ORDER BY __ingest_ts DESC LIMIT 500;                       -- page 1
SELECT * FROM logs.requests WHERE __ingest_ts < '<last_ts>' ORDER BY __ingest_ts DESC LIMIT 500;  -- page 2

Performance (essentials)

  • Always LIMIT (early termination); filter on partition keys first (__ingest_ts range), then add predicates.
  • Narrow time ranges; compact tables (file count dominates latency — enable automatic compaction in Basin Catalog).
  • Read response metrics (files_scanned, bytes_scanned) to tune. Full guidance: limitations-best-practices doc.

Basin Pipelines → Basin SQL

After npx wrangler basin pipelines setup (Basin Catalog destination), wait for first flush (3–7 min), then query the table. See pipelines/patterns.md.

See Also

Source: SKILL.md on GitHub

No alertstoday3 checks · Risk SAFE
  • Gen Agent Trust Hubtoday

    This skill provides documentation and implementation patterns for Cloudflare Basin analytics workflows, including Pipelines, Catalog, and SQL querying. No security issues were detected, and the skill correctly leverages standard Cloudflare CLI tools and official API endpoints while following best practices for credential management.

  • Sockettoday

    No alerts

  • Snyktoday

    Risk: LOW · No issues

Signed by skilld at 41e0d19. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 5 hours ago.

Activeupdated 6 hours ago

README badge

README badge for cloudflare/skills/basin