How to get Google Analytics 4 and Search Console data into Google Sheets
Short answer: Google Analytics™ 4 and Google Search Console™ can each export a table to Google Sheets™ once, by hand. Nothing in either one keeps that copy up to date. For numbers that refresh, the free way is a short Apps Script: it asks the Google Analytics Data API or the Search Console API for the report, writes the rows into a tab, and a time-driven trigger runs it every morning. An add-on such as TrafficLane does the same without code, for both sources in one panel.
1. The exports built into GA4 and Search Console
- GA4™ reports. In a report, the share icon opens Download File, with a PDF, a CSV file and, in many reports, an export to Google Sheets. Explorations have their own export button. Each export is a copy of the table on screen, at that moment.
- Search Console™. In Performance, the Export button saves the tables to Google Sheets, Excel or a CSV file. Each table holds up to 1,000 rows, the same as on screen.
Both are fine for a one-off look. Next week, you export again, and paste the new numbers where the old ones were.
Looker Studio™ connects to both GA4™ and Search Console™ and can email a report on a schedule, but what arrives is a dashboard, not rows in your spreadsheet. Teams that already work in BigQuery can send GA4™ and Search Console™ data there and read it into Sheets with Connected Sheets, where their Google Workspace™ edition includes it; that means a Google Cloud project and SQL.
2. The Apps Script way: GA4 top pages into a tab
- In your spreadsheet, add a tab named
GA4 Top pages, then choose Extensions → Apps Script. - Next to Services on the left, click +, choose Google Analytics Data API and click Add.
- Replace the code with the script below. Put in your property ID, the number from GA4 → Admin → Property details, and click Save.
- Click Run. The first time, Google asks you to allow the script to see your Google Analytics data and your spreadsheets. Then look at the tab.
function importTopPages() {
const propertyId = '123456789'; // GA4 → Admin → Property details
const report = AnalyticsData.Properties.runReport({
dateRanges: [{ startDate: '28daysAgo', endDate: 'yesterday' }],
dimensions: [{ name: 'pagePath' }],
metrics: [{ name: 'screenPageViews' }, { name: 'sessions' }],
orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
limit: 1000
}, 'properties/' + propertyId);
const header = report.dimensionHeaders.map(h => h.name)
.concat(report.metricHeaders.map(h => h.name));
const rows = (report.rows || []).map(r =>
r.dimensionValues.map(v => v.value).concat(r.metricValues.map(v => Number(v.value))));
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('GA4 Top pages');
sheet.clearContents();
sheet.getRange(1, 1, rows.length + 1, header.length).setValues([header].concat(rows));
}
The dimension and metric names are the API names, listed in Google’s GA4 Dimensions & Metrics Explorer: date, sessionDefaultChannelGroup, activeUsers, keyEvents and so on. Date ranges take dates such as 2026-10-01, or today, yesterday and NdaysAgo.
3. Search Console from Apps Script
Apps Script has no built-in service for Search Console™, so the script calls its API directly:
function importQueries() {
const site = 'sc-domain:example.com'; // or 'https://www.example.com/'
const url = 'https://www.googleapis.com/webmasters/v3/sites/' +
encodeURIComponent(site) + '/searchAnalytics/query';
const res = UrlFetchApp.fetch(url, {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
payload: JSON.stringify({ startDate: '2026-10-01', endDate: '2026-10-31',
dimensions: ['query'], rowLimit: 1000 })
});
const rows = (JSON.parse(res.getContentText()).rows || [])
.map(r => [r.keys[0], r.clicks, r.impressions, r.ctr, r.position]);
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Search queries');
sheet.clearContents();
sheet.getRange(1, 1, rows.length + 1, 5)
.setValues([['Query', 'Clicks', 'Impressions', 'CTR', 'Position']].concat(rows));
}
For this one the script needs Search Console’s permission written into its manifest. In Project Settings, tick Show “appsscript.json” manifest file in editor, and add an oauthScopes list with https://www.googleapis.com/auth/webmasters.readonly, https://www.googleapis.com/auth/spreadsheets and https://www.googleapis.com/auth/script.external_request. Once that list is there, Google asks for exactly those permissions.
4. Make it refresh every morning
- In the Apps Script editor, click Triggers (the clock icon on the left), then Add Trigger.
- Choose the function, such as
importTopPages, the event source Time-driven, the type Day timer and a time such as 6am to 7am. - Click Save.
From then on Google runs the script once a day at some point in that hour, while the spreadsheet is closed. A Week timer or Month timer refreshes less often.
5. Where the simple script falls short
- Dates arrive as text. GA4™ returns the
datedimension as20261112. Charts and date formulas need it turned into a real date first. - Search queries can turn into formulas. A query that starts with
=,+or-, written withsetValues, is read by Sheets as a formula or a number. Search queries are typed by strangers, so each value has to be written as text. - Row limits. A GA4™ request returns up to 10,000 rows unless you ask for more with
limitandoffset. A Search Console™ request returns up to 25,000 rows; beyond that you ask again withstartRow. - Quotas. Google limits how much each GA4™ property can be asked per hour and per day, and Search Console™ has its own limits. A run that hits one fails, and the tab keeps last week’s numbers without saying so.
- Errors go quietly. When a run fails, Google emails a failure summary to the person who made the trigger, by default once a day. The people who read the tab do not know the numbers are old.
- Sampling and thresholds. GA4™ can sample very large reports and hides rows with very few users. The API says so in its answer; the script above throws that away, so totals can differ from the GA4™ screens with no word why.
- Fresh data is not final. Search Console™ data of the last two or three days is still being collected, and by default the API leaves it out. Last week’s numbers can still change.
- Comparisons are a second program. “This month against the month before” means a second request, then matching every page or query row by row.
- One script per spreadsheet. Property IDs, site addresses and field names live in code. Each client sheet needs its own copy, and every change to a report means editing it.
None of this is hard on its own. Together, it is a small program to keep up every time someone asks for one more report.
6. With an add-on: TrafficLane
TrafficLane is our Google Sheets™ add-on for this job. It does what the scripts above leave to you:
- GA4™ and Search Console™ in one panel, listing the properties and sites your Google account can already see. Both permissions are read-only.
- Eleven ready-made reports: traffic by day, top pages, channels and sources, countries, devices and landing pages from GA4™; queries, pages, countries, devices and days from Search Console™. Any dates, filters, up to 25,000 rows.
- Each report in its own tab, with real dates, number formats, a bold, frozen header and an “Updated … by TrafficLane” note. Search queries are always written as text. A tab holding your own data is never overwritten.
- Errors in plain words, in the panel and in the tab, and the last good data stays. Google’s sampling and threshold remarks go into the note.
- Refresh now by hand, free. With Pro, a schedule: every day, on the weekdays you tick, or monthly, at the hour you choose, even when the spreadsheet is closed.
- Pro also adds comparisons with the period before or a year earlier, custom GA4™ reports with names checked against your property, up to 30 reports and a run log. Pro is $4.99 a month or $39 a year.
How to set it up: TrafficLane help.
Questions
Can Google Analytics 4 export to Google Sheets automatically?
Not by itself. An export from a GA4 report is a one-time copy. For a copy that refreshes, you need an Apps Script with a time-driven trigger, or an add-on.
How do I get Search Console data into Google Sheets?
For a one-time copy, use Export in the Performance report, up to 1,000 rows per table. For more rows or a regular refresh, call the Search Console API from Apps Script, or use an add-on.
How many rows can the Search Console API return?
Up to 25,000 rows per request, and more by asking again with startRow. It keeps about 16 months of data.
Why do numbers from the GA4 API differ from the GA4 screens?
Usually because of data thresholds, which hide rows with very few users, or sampling on very large reports. The API says so in the metadata of its answer, which a simple script does not read.