# Google Analytics 4 Exporter: GA4 Reports to CSV, Excel & JSON (`aaronoptimus/google-analytics-4-exporter`) Actor

Export Google Analytics 4 reports from your own GA4 properties with Google's official Data API: traffic, channels, landing pages, pages, events, ecommerce, realtime or any custom report. Many properties per run, period comparisons, flat rows. $0.50 per 1,000 rows plus a small start fee.

- **URL**: https://apify.com/aaronoptimus/google-analytics-4-exporter.md
- **Developed by:** [Aaron Tuttle](https://apify.com/aaronoptimus) (community)
- **Categories:** SEO tools, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 report rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

Export Google Analytics 4 reports for **$0.50 per 1,000 rows**: traffic by day, channels, landing pages, events, ecommerce, realtime or any custom report, from one or many GA4 properties in one run, as flat rows ready for Google Sheets, Excel, Looker Studio, BigQuery or your AI agent.

It uses Google's official [GA4 Data API](https://developers.google.com/analytics/devguides/reporting/data/v1) with **your own Google service account**, so it reads only the properties you give it access to. No scraping, no browser, no proxies, and no personal data: GA4 reports are aggregated counts, and Google hides small groups of users itself.

### What GA4 data can you export?

Pick any of 15 ready-made reports, or build your own from any GA4 dimensions and metrics (including your custom dimensions such as `customEvent:plan_type`).

| Report | One row per | Main columns |
|---|---|---|
| Traffic overview by day | day | active users, new users, sessions, engaged sessions, engagement rate, avg. session duration, views, events, key events, revenue |
| Traffic by channel | channel (Organic Search, Paid Search, Social …) | sessions, users, engagement, key events, key event rate, revenue |
| Source / medium | source + medium | same as channels |
| Campaigns (UTM) | campaign + source + medium | same as channels |
| Landing pages | landing page with query string | sessions, users, engagement and bounce rate, key events, revenue |
| Pages and screens | page path + title | views, users, views per user, engagement time, events, key events |
| Events | event name | event count, users, events per user, key events, event value |
| Key events (conversions) by channel | key event + channel | key events, users, revenue |
| Devices | device + operating system + browser | users, sessions, engagement, key events, revenue |
| Countries / Cities | country (and region, city) | users, sessions, engagement, key events, revenue |
| New vs returning | new / returning | users, sessions, engagement, key events, revenue |
| Ecommerce: products | product name + ID | items viewed, added to cart, purchased, item revenue |
| Ecommerce: revenue by day | day | purchases, purchase revenue, total revenue, avg. purchase, purchasers, first-time purchasers |
| Realtime | country + device + screen, last 30 minutes | active users, views, events, key events |

**What makes this exporter different**

- **Many properties per run.** List property IDs, or leave the list empty to export every property your service account can read. Ideal for agencies.
- **Period comparisons built in.** Add `sessionsPrevious` and `sessionsChangePct` (and the same for every metric) next to each row, against the previous period or the same dates last year.
- **Split any report by day, week or month** for time series (e.g. channels per day).
- **Simple filters** like `country==United States`, `pagePath=@/blog` or `sessions>100`.
- **Friendly errors.** Typos get suggestions ("did you mean sessions?"), and missing access or a turned-off API tells you exactly what to click.
- **Totals, sampling and quota** for every report are in the run summary.

### How much does it cost to export GA4 data?

You pay per report row delivered, plus a small start fee. There are no platform fees: compute is included.

| | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| Per 1,000 report rows | $0.60 | $0.50 | $0.45 | $0.40 |
| Run start | $0.002 | $0.002 | $0.002 | $0.002 |

- The default run (3 reports, last 28 days, one property) is usually 50–300 rows: **about $0.03–$0.15**.
- Apify's $5 free plan ≈ 8,300 report rows.
- Reports that fail, rows over your limits, and runs without credentials cost nothing beyond the start fee. (If a long report fails halfway, you pay only for the rows already delivered.) Set **Maximum cost per run** in Apify and the run stops cleanly at that amount.
- Google doesn't charge for the GA4 Data API; it only limits how much each property can request per day (about 200,000 "tokens", far more than daily exports need).

### How to connect your GA4 property (5 minutes, once)

1. **Turn on the API.** In [Google Cloud Console](https://console.cloud.google.com/) pick or create a project, search for **Google Analytics Data API** and click **Enable**. Also enable **Google Analytics Admin API** if you want property names, local time zones, or "export all my properties".
2. **Create a service account.** Go to **IAM & Admin → Service accounts → Create service account**, give it a name (e.g. `ga4-reader`) and click **Done**. You don't need to give it any Cloud roles.
3. **Download its key.** Open the service account → **Keys → Add key → Create new key → JSON**. A `.json` file downloads.
4. **Give it access in Google Analytics.** In GA4 open **Admin → Property access management → + → Add users**, paste the service account e-mail (it ends in `iam.gserviceaccount.com`), choose **Viewer**, and click **Add**. Repeat for each property, or add it once at account level.
5. **Run.** Paste the whole `.json` file into **Service account key (JSON)**, enter your property ID (Admin → Property details, a number like `123456789`), and click **Start**.

The key is stored by Apify as an encrypted secret input, used only to sign in to Google, and never logged or written to the results. To revoke access at any time, delete the key in Google Cloud or remove the user in Google Analytics.

If your organisation blocks service account keys, use **Sign in with OAuth instead**: an OAuth client ID, client secret and a refresh token with the `analytics.readonly` scope.

### How to export GA4 reports

**Input example**: last month's channels and landing pages for two properties, compared with the previous month:

```json
{
    "serviceAccountKey": "{ \"type\": \"service_account\", … }",
    "propertyIds": ["123456789", "987654321"],
    "reports": ["channels", "landingPages"],
    "dateRange": "lastMonth",
    "comparison": "previousPeriod",
    "filters": ["country==United States|Canada"]
}
```

**Output example** (one row of the channels report):

```json
{
    "propertyId": "123456789",
    "propertyName": "Example shop",
    "report": "channels",
    "startDate": "2026-09-01",
    "endDate": "2026-09-30",
    "previousStartDate": "2026-08-02",
    "previousEndDate": "2026-08-31",
    "currency": "USD",
    "sessionDefaultChannelGroup": "Organic Search",
    "sessions": 18420,
    "activeUsers": 14211,
    "newUsers": 11037,
    "engagedSessions": 12102,
    "engagementRate": 0.657004,
    "averageSessionDuration": 142.318,
    "keyEvents": 512,
    "sessionKeyEventRate": 0.027796,
    "totalRevenue": 23110.5,
    "sessionsPrevious": 16890,
    "sessionsChangePct": 9.1
}
```

The values above are illustrative. Column names are GA4's own API names, so they match Google's documentation and Looker Studio. Dates are `YYYY-MM-DD` (weeks `2026-W40`, months `2026-10`); rates are fractions (0.657 = 65.7%); durations are seconds.

**Custom report**: any combination of up to 9 dimensions and 10 metrics. Fill in **Custom report: metrics** and **dimensions**, or pass several in **More custom reports (JSON)**:

```json
[
    { "name": "blog", "dimensions": ["pagePath", "sessionSource"], "metrics": ["screenPageViews", "userEngagementDuration"], "filters": ["pagePath=^/blog"], "orderBy": "-screenPageViews", "limit": 500 },
    { "name": "live", "realtime": true, "dimensions": ["unifiedScreenName"], "metrics": ["activeUsers"] }
]
```

**Filters** (one per line, all must match; text ignores case; a filter is skipped, with a note in the log, for reports it can't apply to, e.g. `sessions>100` on the products report or most fields on realtime): `==` equals (`country==Germany|France` for several values), `!=` not equal, `=@` contains, `!@` doesn't contain, `=^` starts with, `=$` ends with, `=~` regex, `!~` not regex, and `>`, `>=`, `<`, `<=` for metrics (`sessions>100`).

**Run summary**: the `OUTPUT` record in the key-value store lists every report with its row count, how many rows GA4 had, totals (and previous-period totals), notes about sampling or thresholds, the remaining property quota, and any error.

### Google Analytics 4 API: Python, JavaScript, Zapier, Make and AI agents (MCP)

Run it from your code with the Apify API ([Python](https://docs.apify.com/api/client/python) or [JavaScript](https://docs.apify.com/api/client/js) client), on a schedule (e.g. every Monday, exported to Google Sheets with Apify's integration), or from Zapier, Make and n8n.

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("aaronoptimus/google-analytics-4-exporter").call(run_input={
    "serviceAccountKey": open("ga4-key.json").read(),
    "propertyIds": ["123456789"],
    "reports": ["trafficOverview", "channels"],
    "dateRange": "last7Days",
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["report"], row.get("date") or row.get("sessionDefaultChannelGroup"), row["sessions"])
```

AI agents (Claude, Cursor, ChatGPT and others that support MCP) can call it through Apify's MCP server: `https://mcp.apify.com?tools=aaronoptimus/google-analytics-4-exporter`.

### What this tool doesn't do

- It doesn't read GA4 properties you can't access. Every property needs your service account (or OAuth user) added in Google Analytics.
- No Universal Analytics (`UA-` IDs). Google shut it down in 2024.
- No user-level or personal data. GA4 reports are aggregated, and Google applies its own privacy thresholds (noted in the run summary when it does).
- No raw event export. For event-level data, use GA4's free BigQuery export.

### FAQ

**Do I need a Google Cloud bill or credit card?** No. The GA4 Data API is free, and creating a service account doesn't need billing.

**Why does my report have fewer rows than the GA4 interface?** GA4 hides rows with only zeros (turn on **Keep rows where every metric is 0**), applies privacy thresholds to small groups, and the tool stops at **Max rows per report** (10,000 by default).

**Which time zone are the dates in?** Each property's own time zone (read with the Admin API). Without the Admin API, dates are counted in UTC and the log says so.

**What if I get "has no access to property"?** Add the e-mail shown in the message as a Viewer under Admin → Property access management, and check that you used the numeric property ID (not the `G-` measurement ID).

**Can I export many properties at once?** Yes. List them, or leave the list empty to export every property the service account can read.

### Related tools

- [Google Trends Scraper](https://apify.com/aaronoptimus/google-trends-scraper): search interest over time and by region.
- [Google Autocomplete Scraper](https://apify.com/aaronoptimus/google-autocomplete-scraper): keyword ideas.
- [Google Ads Transparency Scraper](https://apify.com/aaronoptimus/google-ads-transparency-scraper): competitors' Google ads.
- [PageSpeed Insights Bulk Audit](https://apify.com/aaronoptimus/pagespeed-insights-bulk-audit): Core Web Vitals for many pages.

Found a problem or need a field? Open an issue on the Issues tab. If the Actor saves you time, a short review helps others find it.

This Actor is an independent tool and is not affiliated with, endorsed by or sponsored by Google. Google Analytics is a trademark of Google LLC.

# Changelog

This Actor's version history is a separate document: https://apify.com/aaronoptimus/google-analytics-4-exporter/changelog.md

# Actor input Schema

## `serviceAccountKey` (type: `string`):

Paste the whole .json key file of a Google Cloud service account (Google Cloud → IAM & Admin → Service accounts → Keys → Add key → JSON). Then add the service account e-mail as Viewer in Google Analytics (Admin → Property access management). The key is stored encrypted, used only to sign in to Google, and never logged or saved in the results. Step-by-step guide in the README.

## `propertyIds` (type: `array`):

Numeric GA4 property IDs, one per line (Admin → Property details, e.g. 123456789), or Google Analytics URLs containing p123456789. Leave empty to export every property the service account can read (needs the Google Analytics Admin API turned on).

## `reports` (type: `array`):

Pick one or more. Each report gives one row per value (day, channel, page, …) with the main GA4 metrics as columns. Leave empty to run only your custom report.

## `dateRange` (type: `string`):

Days are counted in each property's own time zone. "Last N days" end yesterday (today's data is still being processed by Google). Pick "Custom" to use the start and end date below.

## `startDate` (type: `string`):

YYYY-MM-DD, or relative: today, yesterday, 30daysAgo. Used when the date range is "Custom".

## `endDate` (type: `string`):

YYYY-MM-DD, or relative: today, yesterday, 7daysAgo. Used when the date range is "Custom".

## `comparison` (type: `string`):

Adds <metric>Previous and <metric>ChangePct (percent change) columns to every row. Daily, weekly and monthly rows are paired by position (day 1 with day 1, and so on).

## `splitBy` (type: `string`):

Adds a time column to the ready-made reports, e.g. channels per day. Multiplies the number of rows (and the cost) by the number of days, weeks or months.

## `filters` (type: `array`):

Optional, one per line, applied to every report (all must match). Examples: country==United States, country==Germany|France, pagePath=@/blog (contains), pagePath=^/shop (starts with), sessionSource=~goo.\* (regex), sessionDefaultChannelGroup!=Direct, sessions>100. Text matching ignores upper/lower case.

## `metrics` (type: `array`):

GA4 metric API names, up to 10, e.g. sessions, activeUsers, screenPageViews, keyEvents, totalRevenue, or your custom metrics. Full list: developers.google.com/analytics/devguides/reporting/data/v1/api-schema

## `dimensions` (type: `array`):

GA4 dimension API names, up to 9, e.g. date, sessionSource, pagePath, country, deviceCategory, customEvent:your_parameter.

## `orderBy` (type: `string`):

A metric or dimension of the custom report; put a minus in front for descending, e.g. -sessions. Default: by date if the report has one, else by the first metric, largest first.

## `customReports` (type: `array`):

For several custom reports in one run: a list like \[{"name": "blog", "dimensions": \["pagePath"], "metrics": \["screenPageViews"], "filters": \["pagePath=^/blog"], "orderBy": "-screenPageViews", "limit": 500}]. Add "realtime": true for a realtime report (last 30 minutes).

## `maxRowsPerReport` (type: `integer`):

Upper limit per report and property (top rows by the report's sort order). Up to 100,000 (50,000 with a comparison). Each row is one charged result.

## `keepEmptyRows` (type: `boolean`):

GA4 leaves out rows with only zeros by default (e.g. days without visits). Turn on for a complete daily series.

## `currencyCode` (type: `string`):

Optional 3-letter code (e.g. USD, EUR) to convert revenue metrics. Default: each property's own currency (shown in the currency column).

## `maxConcurrency` (type: `integer`):

How many reports run at the same time. Google allows 10 concurrent requests per property; 3 is gentle on your quota.

## `oauthClientId` (type: `string`):

Only if your organisation blocks service account keys: an OAuth client ID with a refresh token that has the analytics.readonly scope. Leave empty when you use a service account key.

## `oauthClientSecret` (type: `string`):

The client secret of the OAuth client above.

## `oauthRefreshToken` (type: `string`):

A refresh token for the scope https://www.googleapis.com/auth/analytics.readonly (for example from Google's OAuth Playground with your own client).

## Actor input object example

```json
{
  "reports": [
    "trafficOverview",
    "channels",
    "landingPages"
  ],
  "dateRange": "last28Days",
  "comparison": "none",
  "splitBy": "none",
  "maxRowsPerReport": 10000,
  "keepEmptyRows": false,
  "maxConcurrency": 3
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "reports": [
        "trafficOverview",
        "channels",
        "landingPages"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("aaronoptimus/google-analytics-4-exporter").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "reports": [
        "trafficOverview",
        "channels",
        "landingPages",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("aaronoptimus/google-analytics-4-exporter").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "reports": [
    "trafficOverview",
    "channels",
    "landingPages"
  ]
}' |
apify call aaronoptimus/google-analytics-4-exporter --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,aaronoptimus/google-analytics-4-exporter"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/TCHoKekFVOd2jJIkx/builds/GBM6kOzjm40bLXSk2/openapi.json
