GraphQL Analytics API Patterns & Best Practices
Time-Series Queries
Use time dimension granularity matching your range (see Best Practices below).
query TrafficTimeSeries($zoneTag: string!, $start: Time!, $end: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
httpRequestsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }
limit: 1000
orderBy: [datetimeFiveMinutes_ASC] # or datetimeHour_ASC for longer ranges
) {
count
dimensions { datetimeFiveMinutes }
sum { edgeResponseBytes }
ratio { status4xx status5xx }
}
}
}
}Top-N Queries
Top Countries by Request Count
query TopCountries($zoneTag: string!, $start: Time!, $end: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
httpRequestsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }
limit: 10
orderBy: [count_DESC]
) {
count
dimensions { clientCountryName }
}
}
}
}Use orderBy: [sum_edgeResponseBytes_DESC] for top paths by bandwidth. Add edgeResponseStatus_geq: 400 to the filter for top error status codes.
Workers Analytics
query WorkersOverview($accountTag: string!, $start: Time!, $end: Time!) {
viewer {
accounts(filter: { accountTag: $accountTag }) {
workersInvocationsAdaptive(
filter: { datetime_gt: $start, datetime_lt: $end }
limit: 100
orderBy: [sum_requests_DESC]
) {
sum { requests errors subrequests wallTime }
quantiles { cpuTimeP50 cpuTimeP99 wallTimeP50 wallTimeP99 }
dimensions { scriptName }
}
}
}
}Filter by scriptName for a specific Worker. Add datetimeFiveMinutes dimension + orderBy: [datetimeFiveMinutes_ASC] for error rate over time.
Firewall / Security
query RecentFirewallEvents($zoneTag: string!, $start: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
firewallEventsAdaptive(
filter: { datetime_gt: $start }
limit: 50
orderBy: [datetime_DESC]
) {
action source clientIP clientCountryName userAgent
clientRequestHTTPHost clientRequestPath ruleId datetime
}
}
}
}For aggregated firewall stats, use firewallEventsAdaptiveGroups with action: "block" filter and group by ruleId, source, datetimeHour.
DNS Analytics
query DNSQueryVolume($zoneTag: string!, $start: Time!, $end: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
dnsAnalyticsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }
limit: 500
orderBy: [datetimeFiveMinutes_ASC]
) {
count
dimensions { datetimeFiveMinutes }
}
}
}
}Storage Analytics (Account-Scoped)
R2, KV, and D1 use date (Date type) filters instead of datetime (Time type).
# R2 operations
r2OperationsAdaptiveGroups(filter: { date_geq: $start, date_leq: $end }, limit: 100, orderBy: [date_DESC]) {
dimensions { date bucketName actionType }
sum { requests }
}
# KV operations
kvOperationsAdaptiveGroups(filter: { date_geq: $start, date_leq: $end }, limit: 100, orderBy: [date_DESC]) {
dimensions { date actionType }
sum { requests }
}
# D1 analytics
d1AnalyticsAdaptiveGroups(filter: { date_geq: $start, date_leq: $end }, limit: 100, orderBy: [date_DESC]) {
dimensions { date databaseId }
sum { readQueries writeQueries rowsRead rowsWritten }
}Cache Analytics
query CacheStatusBreakdown($zoneTag: string!, $start: Time!, $end: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
httpRequestsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }
limit: 20
orderBy: [count_DESC]
) {
count
dimensions { cacheStatus }
sum { edgeResponseBytes }
}
}
}
}For cache hit ratio over time, use aliases to query the same dataset twice — once with cacheStatus: "hit" filter and once without — then compute the ratio client-side.
Multi-Dataset Queries
A single request can query multiple datasets, avoiding extra HTTP round-trips:
query DashboardOverview($zoneTag: string!, $start: Time!, $end: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
httpTraffic: httpRequestsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }, limit: 1
) { count sum { edgeResponseBytes } ratio { status4xx status5xx } }
firewallEvents: firewallEventsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }, limit: 5, orderBy: [count_DESC]
) { count dimensions { action source } }
dnsQueries: dnsAnalyticsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }, limit: 1
) { count }
}
}
}AI & Gateway Analytics
# Workers AI inference
aiInferenceAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }, limit: 100, orderBy: [datetimeHour_DESC]
) {
count
sum { totalInputTokens totalOutputTokens totalRequestBytesIn }
dimensions { modelId datetimeHour }
}
# AI Gateway requests
aiGatewayRequestsAdaptiveGroups(
filter: { datetime_gt: $start, datetime_lt: $end }, limit: 100, orderBy: [datetimeHour_DESC]
) {
count
dimensions { gateway provider model datetimeHour }
sum { cachedTokensIn cachedTokensOut uncachedTokensIn uncachedTokensOut }
}Both are account-scoped — nest under accounts(filter: { accountTag: $accountTag }).
Best Practices
Always include time filters. Queries without time filters scan all data and are slow/expensive.
Match time granularity to range:
| Time Range | Recommended Dimension |
|---|---|
| < 6 hours | datetimeMinute or datetimeFiveMinutes |
| 6-48 hours | datetimeFiveMinutes or datetimeFifteenMinutes |
| 2-14 days | datetimeHour |
| 14+ days | date |
Use aliases for querying the same dataset with different filters in one request.
Request only needed fields. Extra dimensions and metrics increase query cost.
See Also
- README.md - Overview, decision tree, dataset index
- api.md - Query structure, aggregation fields, filtering operators
- configuration.md - Authentication, client setup, introspection queries
- gotchas.md - Rate limits, sampling, troubleshooting