How to Export Cloudflare Analytics to CSV
You need Cloudflare traffic numbers in a spreadsheet: for a monthly report, a board deck, or a comparison the dashboard doesn’t do.
This guide exports daily analytics to CSV with one API call and jq. It works on every Cloudflare plan.
What you’ll get
One row per day:
date,requests,bytes,cachedRequests,cachedBytes,threats,pageViews,uniques
2026-07-06,1843201,40211834112,1520339,35120993280,1203,210442,48211
...
What you need
curlandjqinstalled- A Cloudflare API token with Zone → Analytics → Read
- Your zone ID (on the zone’s Overview page)
export CF_API_TOKEN="your-token"
export ZONE_ID="your-zone-id"
Step 1: Pick your date range
The daily dataset (httpRequests1dGroups) only goes back as far as your plan allows. Check your exact limit first; if you ask for older dates, the API returns an error.
START="2026-07-01" # inclusive
END="2026-07-14" # exclusive
Step 2: Run the export
curl -s https://api.cloudflare.com/client/v4/graphql \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data "$(jq -n --arg zone "$ZONE_ID" --arg start "$START" --arg end "$END" '{
query: "query ($zoneTag: string, $start: Date, $end: Date) { viewer { zones(filter: {zoneTag: $zoneTag}) { httpRequests1dGroups(limit: 1000, filter: {date_geq: $start, date_lt: $end}, orderBy: [date_ASC]) { dimensions { date } sum { requests bytes cachedRequests cachedBytes threats pageViews } uniq { uniques } } } } }",
variables: { zoneTag: $zone, start: $start, end: $end }
}')" \
| jq -r '
if .errors then error(.errors | tostring) else . end
| ["date","requests","bytes","cachedRequests","cachedBytes","threats","pageViews","uniques"],
(.data.viewer.zones[0].httpRequests1dGroups[]
| [.dimensions.date, .sum.requests, .sum.bytes, .sum.cachedRequests,
.sum.cachedBytes, .sum.threats, .sum.pageViews, .uniq.uniques])
| @csv' > cloudflare-traffic.csv
Two details that save debugging time:
- Building the request body with
jq -navoids shell-quoting bugs in the GraphQL string. - The
if .errorscheck makes the command fail loudly. GraphQL returns HTTP 200 even when the query fails.
Step 3: Open it
head cloudflare-traffic.csv
Import into Google Sheets, Excel, or a database. A few useful derived columns:
| Metric | Formula |
|---|---|
| Cache hit ratio (requests) | cachedRequests / requests |
| Bandwidth saved | cachedBytes / bytes |
| GB served | bytes / 1e9 |
Common errors
- An error about the start date being too old — you’ve gone past your plan’s retention (
notOlderThan). - An error about the time range being too wide — your range exceeds
maxDuration. Split it into smaller windows. - Empty result — wrong zone ID, or the token doesn’t have access to that zone.
Why run this regularly
This export only reaches back as far as Cloudflare keeps data. If you run it once a month and append the results, the CSV becomes your own long-term history, and next year you can compare traffic year over year.
To automate it, schedule the same query in a Worker and store the results in R2.
Or skip the scripts
MetricKeeper keeps this history for you. Connect a read-only token, and it archives your Cloudflare analytics on a schedule with export built in.