Skip to main content

analytics-cloudflare

Cloudflare GraphQL Analytics for zone traffic, firewall events, Workers metrics, and schema exploration. Use when querying Cloudflare analytics data or exploring the GraphQL API.

Source facts

Repository
cloudthinker-ai/CloudSkills
Last source activity
April 5, 2026 at 03:44
Detected SKILL.md language
English
Stars
6
Forks
3

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
analytics-cloudflare
description
Cloudflare GraphQL Analytics for zone traffic, firewall events, Workers metrics, and schema exploration. Use when querying Cloudflare analytics data or exploring the GraphQL API.
connection_type
cloudflare
preload
false
# Analytics Cloudflare ## Discovery <critical> **If no `[cached_from_skill:analytics-cloudflare:discover]` context exists, run discovery first:** ```bash bun run ./_skills/connections/cloudflare/analytics-cloudflare/scripts/discover.ts bun run ./_skills/connections/cloudflare/analytics-cloudflare/scripts/discover.ts --max-zones 5 bun run ./_skills/connections/cloudflare/analytics-cloudflare/scripts/discover.ts --zone example.com ``` Output is auto-cached. </critical> **What discovery provides:** - `accounts`: List of Cloudflare accounts with `id`, `name` - `zones`: Zone details with `id`, `name`, `status`, `accountId`, `accountName`, `plan` - `dataRetention`: Retention windows by data type (DNS, HTTP, firewall, workers) based on plan tier - `availableDatasets`: GraphQL datasets with `name`, `scope` (account/zone), `variableType` (Date!/Time!), `retention` - `queryHints`: - `criticalRequirement`: Must call `graphql_zones_list()` before DNS queries - `correctWorkflow`: Step-by-step workflow for queries - `variableTypes`: Which datasets use `Date!` vs `Time!` - `scopeRequirements`: Which datasets are account-level vs zone-level - `safeDateRanges`: Pre-calculated safe date ranges based on retention **Why run discovery:** - Get `accountId` required for DNS/Workers queries - Get `zoneId` required for HTTP/Firewall queries - Know data retention limits (7-62 days depending on plan) - Understand correct variable types (Date! vs Time!) - Avoid 0-record results from incorrect scope or context **Output Formatting:** Use `format()` for token-efficient output (40-60% token savings): ```typescript import { format } from "@connections/_utils/format"; console.log(format(result)); // CORRECT - Uses TOON encoding // console.log(JSON.stringify(result, null, 2)); // WRONG - Wastes tokens ``` --- ## Critical Rules 🚨 **MUST call `graphql_zones_list({ name: 'domain.com' })` BEFORE querying DNS analytics** - Required to establish zone context - Hardcoded account IDs → returns 0 records - Without zone context → returns 0 records 🚨 **CRITICAL: `graphql_graphql_query` Response Handling** The `graphql_graphql_query` tool returns a **STRING** containing: 1. JSON response data 2. A markdown link to GraphQL Explorer (appended after `\n\n`) Example response format: ``` {"data":{...},"errors":null} **[Open in GraphQL Explorer](https://graphql.cloudflare.com/explorer?query=...)** ``` **MANDATORY parsing pattern:** ```typescript // ✅ CORRECT - Extract JSON before parsing const resultStr = result as string; const jsonPart = resultStr.split('\n\n')[0]; // Remove markdown link const parsed = JSON.parse(jsonPart); // ❌ WRONG - Parsing full string fails with "Unrecognized token '*'" const parsed = JSON.parse(result as string); // Error! ``` **Always check for GraphQL errors before accessing data:** ```typescript if (parsed.errors && parsed.errors.length > 0) { console.error('GraphQL Errors:', format(parsed.errors)); process.exit(1); } const data = parsed.data?.viewer?.accounts?.[0]?.dnsAnalyticsAdaptiveGroups || []; ``` **Format requirements:** - DNS: `Date!` format (`"2026-01-27"`) - HTTP/Firewall/Workers: `Time!` format (`"2026-01-27T00:00:00Z"`) **Scope requirements:** - DNS & Workers: Account-level (`viewer.accounts`) - HTTP & Firewall: Zone-level (`viewer.zones`) **Retention (Free plan):** - DNS: 7 days only - HTTP: 31 days - Firewall/Workers: 30 days --- ## Verified Working Example ```typescript import { graphql_zones_list, graphql_graphql_query } from '@connections/cloudflare'; import { format } from '@connections/_utils/format'; async function queryDNS() { // Step 1: CRITICAL - Establish zone context const zonesResult = await graphql_zones_list({ name: 'cloudthinker.io' }); const zones = typeof zonesResult === 'string' ? JSON.parse(zonesResult) : zonesResult; const accountId = zones.zones[0].account.id; // Step 2: Query with Date! format const query = `query($accountTag: string!, $start: Date!, $end: Date!) { viewer { accounts(filter: { accountTag: $accountTag }) { dnsAnalyticsAdaptiveGroups( filter: { date_geq: $start, date_leq: $end } limit: 1000 ) { count dimensions { queryName queryType responseCode } } } } }`; const result = await graphql_graphql_query({ query, variables: { accountTag: accountId, start: '2026-01-27', end: '2026-01-28' } }); // Step 3: CRITICAL - Extract JSON before parsing (remove markdown link) const resultStr = result as string; const jsonPart = resultStr.split('\n\n')[0]; const parsed = JSON.parse(jsonPart); // Step 4: Check for GraphQL errors if (parsed.errors && parsed.errors.length > 0) { console.error('GraphQL Errors:', format(parsed.errors)); process.exit(1); } // Step 5: Access the data const dnsData = parsed.data?.viewer?.accounts?.[0]?.dnsAnalyticsAdaptiveGroups || []; if (dnsData.length === 0) { console.log('No DNS data available for the specified period'); process.exit(0); } // Step 6: Display results with format() for token efficiency console.log(format(dnsData)); } queryDNS(); ``` --- ## Query Templates ### DNS Analytics (Account-Level) ```typescript // MUST establish zone context first const zonesResult = await graphql_zones_list({ name: 'example.com' }); const zones = typeof zonesResult === 'string' ? JSON.parse(zonesResult) : zonesResult; const accountTag = zones.zones[0].account.id; const result = await graphql_graphql_query({ query: `query($accountTag: string!, $start: Date!, $end: Date!) { viewer { accounts(filter: { accountTag: $accountTag }) { dnsAnalyticsAdaptiveGroups( filter: { date_geq: $start, date_leq: $end } limit: 1000 ) { count dimensions { queryName queryType responseCode zoneName } } }} }`, variables: { accountTag, start: '2026-01-27', // Date! format, within 7 days end: '2026-01-28' } }); // CRITICAL: Extract JSON before parsing const resultStr = result as string; const jsonPart = resultStr.split('\n\n')[0]; const parsed = JSON.parse(jsonPart); if (parsed.errors) { console.error('Errors:', format(parsed.errors)); process.exit(1); } const dnsData = parsed.data?.viewer?.accounts?.[0]?.dnsAnalyticsAdaptiveGroups || []; ``` ### HTTP Traffic (Zone-Level) ```typescript const zonesResult = await graphql_zones_list({}); const zones = typeof zonesResult === 'string' ? JSON.parse(zonesResult) : zonesResult; const zoneTag = zones.zones.find(z => z.name === 'example.com')?.id; const result = await graphql_graphql_query({ query: `query($zoneTag: string!, $start: Time!, $end: Time!) { viewer { zones(filter: { zoneTag: $zoneTag }) { httpRequests1hGroups( filter: { datetime_geq: $start, datetime_lt: $end } limit: 24 ) { dimensions { datetime } sum { requests bytes cachedRequests } } }} }`, variables: { zoneTag, start: '2026-01-10T00:00:00Z', // Time! format end: '2026-01-11T00:00:00Z' } }); // CRITICAL: Extract JSON before parsing const resultStr = result as string; const jsonPart = resultStr.split('\n\n')[0]; const parsed = JSON.parse(jsonPart); if (parsed.errors) { console.error('Errors:', format(parsed.errors)); process.exit(1); } const httpData = parsed.data?.viewer?.zones?.[0]?.httpRequests1hGroups || []; ``` ### Firewall Events (Zone-Level) ```typescript const result = await graphql_graphql_query({ query: `query($zoneTag: string!, $start: Time!, $end: Time!) { viewer { zones(filter: { zoneTag: $zoneTag }) { firewallEventsAdaptive( filter: { datetime_geq: $start, datetime_leq: $end } limit: 100 ) { action clientCountryName clientIP clientRequestPath datetime } }} }`, variables: { zoneTag, start, end } }); // CRITICAL: Extract JSON before parsing const resultStr = result as string; const jsonPart = resultStr.split('\n\n')[0]; const parsed = JSON.parse(jsonPart); if (parsed.errors) { console.error('Errors:', format(parsed.errors)); process.exit(1); } const firewallEvents = parsed.data?.viewer?.zones?.[0]?.firewallEventsAdaptive || []; ``` ### Workers Analytics (Account-Level) ```typescript const zonesResult = await graphql_zones_list({}); const zones = typeof zonesResult === 'string' ? JSON.parse(zonesResult) : zonesResult; const accountTag = zones.zones[0].account.id; const result = await graphql_graphql_query({ query: `query($accountTag: string!, $scriptName: string!, $start: Time!, $end: Time!) { viewer { accounts(filter: { accountTag: $accountTag }) { workersInvocationsAdaptive( filter: { scriptName: $scriptName, datetime_geq: $start } limit: 100 ) { dimensions { datetime scriptName status } sum { requests errors } } }} }`, variables: { accountTag, scriptName, start, end } }); // CRITICAL: Extract JSON before parsing const resultStr = result as string; const jsonPart = resultStr.split('\n\n')[0]; const parsed = JSON.parse(jsonPart); if (parsed.errors) { console.error('Errors:', format(parsed.errors)); process.exit(1); } const workersData = parsed.data?.viewer?.accounts?.[0]?.workersInvocationsAdaptive || []; ``` --- ## Common Workflows ### Check DNS queries 1. Filter zone: `graphql_zones_list({ name: 'example.com' })` 2. Parse result: `JSON.parse(zonesResult)` 3. Extract account ID: `zones.zones[0].account.id` 4. Query DNS with Date! format 5. **Parse response**: `JSON.parse(result.split('\n\n')[0])` 6. Check errors, then access data ### Check zone traffic 1. Get zone ID: `graphql_zones_list({})` 2. Parse result: `JSON.parse(zonesResult)` 3. Query HTTP with Time! format 4. **Parse response**: `JSON.parse(result.split('\n\n')[0])` 5. Access: `parsed.data.viewer.zones[0].httpRequests1hGroups` ### Find blocked requests 1. Get zone ID: `graphql_zones_list({})` 2. Parse result: `JSON.parse(zonesResult)` 3. Query firewall events 4. **Parse response**: `JSON.parse(result.split('\n\n')[0])` 5. Filter by `action: "block"` --- ## Troubleshooting ### Common Errors | Error | Cause | Fix | |-------|-------|-----| | `JSON Parse error: Unrecognized token '*'` | Markdown link in response | Use `result.split('\n\n')[0]` before parsing |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub