// guide · search console → bigquery

Keep your Search Console data forever in BigQuery

Search Console keeps sixteen months of performance data. Then every day, the oldest day disappears. This guide sets up the export that keeps every day from now on, and shows how to pull the sixteen months you still have before they fall out of the window.

This is the written version of my Search Console warehouse video. Every number below comes from my own export or from Google's own docs, listed at the bottom with the date I checked them.

Search Console is a window, not an archive

Google keeps sixteen months of performance data. Today the window starts about sixteen months back, and every day the oldest day drops off. Not hidden, not archived. Gone, at least from your side.

Here is why that hurts. In February 2025, one of my shops had its best month ever in Search Console: 1,392 clicks. In February 2026, the same month brought 85. The page that earned 917 of those clicks fell from position two to fifteen in February 2026 and never came back. 917 clicks became 2.

In the current Search Console window, that page never shows more than about 250 clicks in a month. The months where it did nine hundred are outside the window. You would never know what you lost, which means you also can't ask what changed.

A change in November only makes sense next to last November. And the November before that. Sixteen months gives you one November. That is the whole argument for this setup.

// the two routes

Two routes, and you want both

Google's export only starts the day you switch it on. There is no backfill from Google. Their help page says it plainly: for data before your setup, use the Search Console API.

16 months
How long Search Console keeps performance data. Then the oldest day disappears.
1,000 rows
What the export button in the Search Console interface gives you, max.
50,000 rows
Per day, per site, per search type, through the API. The bulk export has no row cap at all.
  • The bulk export. A daily dump of your performance data into your own BigQuery project. Every row Search Console has, every day, with no row limit. It starts within 48 hours of setup and catches the day you switch it on. Nothing before that.
  • The Search Console API. Up to 50,000 rows per day per site per search type, and it still reaches sixteen months back. That is your only way to the past, and the past is shrinking by one day every day.

So the order is fixed: turn the bulk export on today, then pull your history through the API into the same place. Every day you wait is a day you can't get back.

Set up the bulk export

Two places, five steps. The Cloud part takes about ten minutes, most of it waiting for billing to activate.

Create a Google Cloud project with billing

Any project works. Billing needs to be on: there is a free usage level, and storage and queries above that free level are charged. Google says this itself on the setup page, and it's why we set an expiration later in this guide.

Enable the BigQuery APIs

In the Cloud Console, go to APIs & Services and enable the BigQuery API and the BigQuery Storage API on that project.

Give Search Console permission to write

Under IAM & Admin, click Grant access and paste this service account:

service account
search-console-data-export@system.gserviceaccount.com

Give it exactly two roles: BigQuery Job User and BigQuery Data Editor. Nothing more. This is the account Search Console uses to write your data, and Google documents these two roles as the requirement.

Set the export destination in Search Console

In Search Console, go to Settings, then Bulk data export, for the property you want. Paste the Cloud project ID, not the project number; the ID is the readable one. Choose a dataset name (the default is searchconsole, and the name always starts with that string) and a location for the dataset. Exporting multiple properties into one project? Give each property its own dataset name. Pick the location with care, because you can't easily change it once exports have started.

Wait for the first export

Search Console runs a test export first. If it succeeds, owners get a mail and exports start within 48 hours, including data for the day you switched it on. The same page in Settings shows the status of recent exports afterwards.

Once the first tables exist, set the partition expiration (see the costs section below). And leave the table schema alone: adding a column to those tables makes the export fail.

The two tables, and why you never add them up

The export lands in your dataset as three tables. Two of them sound like they contain the same thing. They don't.

TableGrainWhat it adds
searchdata_site_impressionAggregated by propertyQuery, country, device, search type
searchdata_url_impressionAggregated by URLThe landing URL, rich result flags, Discover anonymization flag
ExportLogOne row per saved exportWhat was saved, for which date, and when. Failed exports are not logged here

The same click shows up in both data tables, once at property grain and once at URL grain. Add the two totals together and you count every click twice. Pick the table that answers your question and stay in it.

One more trap. Search Console writes each day's data incrementally, so the tables contain rows with repeated keys: the same query, same date, split over several rows. Never read raw rows and trust what you see. Always SUM your metrics and GROUP BY the dimensions you actually want. Google's own query guidelines repeat this for a reason.

Why one trend line across API data and export data lies

Search Console hides rare searches. Google calls them anonymized queries: queries that were not issued by more than a few dozen people over a couple of months. Privacy, fair enough. But the way that hiding works differs per route, and that difference is exactly where bad charts come from.

Report and APIBulk export
Row cap50,000 per day, per site, per search typeNone
ReachSixteen months backFrom the day you switch it on
Anonymized queriesIn the totals, no rowsOwn rows, empty query, page kept

On my own export, this is not a rounding error. From 8 June to 17 October 2026, on my own sites, 60.6 percent of web clicks sat in rows without a search term: 19,152 of 31,606 clicks. On one small site it was 97.1 percent. Those are my numbers from one setup, not a benchmark, but they show the size of the blind spot.

Now the trend line problem. Your API-pulled months contain no anonymized rows. Your export months do. Draw one line across the boundary and it jumps, not because anything happened on your site, but because the export suddenly counts the clicks the API never gave you per row. Compare like with like. And when you load API history into BigQuery, put a source column on it so no query ever mixes the two silently.

Costs: the free level, and the expiration you should set

BigQuery has a free usage level, and a small Search Console export usually stays inside it for storage. Above the free level, storage and queries are charged, and a busy site can grow fast. Three things keep it cheap:

Check what your own setup spends in the Cloud Console billing page after a month. That is your real number, not mine.

Three queries to start with

Replace your-project with your project ID and the site_url with your property: a domain property looks like sc-domain:example.com, a URL-prefix property is the full URL as it is defined in Search Console.

Clicks and impressions by day, properly aggregated

The daily trend for one property. Note the SUMs and the position formula: position is stored zero-based, so the average is the sum divided by impressions, plus one.

sql · daily trend
SELECT
  data_date,
  SUM(impressions) AS impressions,
  SUM(clicks) AS clicks,
  SUM(clicks) / SUM(impressions) AS ctr,
  SUM(sum_top_position) / SUM(impressions) + 1.0 AS avg_position
FROM `your-project.searchconsole.searchdata_site_impression`
WHERE site_url = 'https://www.example.com/'
  AND search_type = 'web'
  AND data_date BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 28 DAY)
                    AND CURRENT_DATE()
GROUP BY data_date
ORDER BY data_date DESC;

Top pages, including the clicks without a search term

This is the query the interface can't give you: pages that earn clicks on rare queries. Those rows have an empty query but the URL table keeps the page, so long-tail pages show up here that never appear in your query report.

sql · top pages, anonymized clicks included
SELECT
  url,
  SUM(clicks) AS clicks,
  SUM(impressions) AS impressions
FROM `your-project.searchconsole.searchdata_url_impression`
WHERE site_url = 'https://www.example.com/'
  AND search_type = 'web'
  AND data_date BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 28 DAY)
                    AND CURRENT_DATE()
GROUP BY url
ORDER BY clicks DESC
LIMIT 20;

Average position per real query

When you want the query list, filter the anonymized rows out, or the empty-string query will sit at the top of your list like a ghost keyword.

sql · top queries with position
SELECT
  query,
  SUM(impressions) AS impressions,
  SUM(clicks) AS clicks,
  SUM(sum_position) / SUM(impressions) + 1.0 AS avg_position
FROM `your-project.searchconsole.searchdata_url_impression`
WHERE site_url = 'https://www.example.com/'
  AND search_type = 'web'
  AND is_anonymized_query = FALSE
  AND data_date BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 28 DAY)
                    AND CURRENT_DATE()
GROUP BY query
ORDER BY clicks DESC
LIMIT 20;

The sixteen months you can still save

The bulk export handles tomorrow. The API handles the past, while it is still there. I'm not going to paste a backfill script I didn't run for you today, so here are the steps instead; they fit in an evening:

Authenticate

Use a Google Cloud account that has access to the property: gcloud auth application-default login is enough for a one-off run, or a service account you added as a user in Search Console.

Walk the window backwards

Start date: today minus sixteen months. For each property and each search type you care about, call the Search Analytics endpoint with date as the dimension, plus query and page for the detail you want, one day at a time.

Paginate to 50,000

Ask for 25,000 rows, then use the row offset to pull the second half. That is the 50,000-per-day-per-search-type ceiling. Bigger sites need both pages; smaller ones finish on the first.

Land it in BigQuery, with a source column

Write the rows into a table next to your export tables and add a column that says where a row came from: api or bulk export. The API months contain no anonymized rows, so any query that mixes both sources without that column will lie to you later.

Check a few days against the interface

Pick three random days, compare totals with the Search Console performance report, and only trust the backfill once those match. The totals should match; the row sums will not, because of the anonymized queries.

The window only shrinks. Sixteen months from your setup day, every day you did not save is gone for good.

// the video

Watch the warehouse episode

The video that goes with this guide: a best month that Google had already pushed out of the sixteen-month window, and the three moves that keep the rest of your history.

Sources, checked 27 October 2026

  • Google, Search Console Help, "Start a new bulk data export", support.google.com/webmasters/answer/12917675: setup steps, service account roles, 48-hour first export, partition expiration of 14 days or longer, schema warning.
  • Google, Search Console Help, "About bulk data export of Search Console data to BigQuery", support.google.com/webmasters/answer/12918484: daily export to BigQuery, free usage level.
  • Google, Search Console Help, "Table guidelines and reference", support.google.com/webmasters/answer/12917991: table names and grain, is_anonymized_query, repeated keys and aggregation advice, forever retention and partition expiration, site_url format.
  • Google, Search Console Help, "Query guidelines and sample queries", support.google.com/webmasters/answer/12917174: always aggregate, filter on date partitions, filter out the zero-length query.
  • Google, Search Console Help, "Export data" and "Performance report overview", support.google.com/webmasters/answer/12919797 and /answer/7576553: 1,000-row interface export, 16-month retention.
  • Google, Search Central blog, "A deep dive into Search Console performance data filtering and limits", 21 October 2022: 50,000 API rows per day per site per search type, anonymized queries included in totals but not in API rows.
  • Google, Search Central blog, "Bulk data export: a new and powerful way to access your Search Console data", 21 February 2023: no daily row limit on the export, anonymized queries filtered out, first export within 48 hours.
  • My own numbers: my Search Console export in BigQuery, 8 June to 17 October 2026, my own sites only. One setup, not a benchmark.

Want this running without the evening of setup?

The warehouse, the daily checks and the agents that read it, installed on your own server. I send a plan that fits what you run.