The Google Search Console UI gives you a window into how your site performs in search—but only 16 months of it, and only in their interface. If you want to track trends over years, build custom dashboards, or trigger alerts when performance tanks, you need to move beyond clicking around. Automating Search Console means pulling data directly into your own systems, where you own the history, can slice it however you want, and can build the reporting that actually matters to your business.
Over my years building production platforms, I've learned that the best insights come when you stop treating analytics as read-only dashboards and start treating them as data sources. Let me walk you through how to do this with Search Console.
Search Analytics API — The Foundation
Google Search Console exposes a REST API that lets you query clicks, impressions, and average position for any combination of page, query, country, device, and date range. This is the engine everything else runs on.
To get started, you'll need:
- A Google Cloud project with the Google Search Console API enabled
- OAuth 2.0 credentials (service account or user credentials)
- Verification that your property is accessible via the API
Here's a practical example. Say you want to fetch the top 25 queries driving traffic to your site in the last 30 days:
const { google } = require('googleapis');
const fs = require('fs');
const webmasters = google.webmasters('v3');
async function fetchSearchAnalytics() {
const auth = new google.auth.GoogleAuth({
keyFile: './credentials.json',
scopes: ['https://www.googleapis.com/auth/webmasters.readonly']
});
const authClient = await auth.getClient();
const endDate = new Date();
const startDate = new Date(endDate.getTime() - 30 * 24 * 60 * 60 * 1000);
const response = await webmasters.searchanalytics.query({
auth: authClient,
siteUrl: 'sc-domain:example.com',
requestBody: {
startDate: startDate.toISOString().split('T')[0],
endDate: endDate.toISOString().split('T')[0],
dimensions: ['query'],
rowLimit: 25,
dataState: 'all'
}
});
return response.data.rows || [];
}
fetchSearchAnalytics().then(rows => {
console.log(JSON.stringify(rows, null, 2));
});
Each row in the response includes clicks, impressions, ctr (click-through rate), and position (average position). You can combine dimensions—query and page together, for instance—to see which pages rank for which keywords. The API also lets you filter by country, device type, and search type (web, image, news, video).
The key limitation: the API only returns data from the last 16 months, and you're rate-limited to around 10 queries per 100 seconds per property. If you have multiple properties, plan your queries accordingly.
Store History — Build Your Own Data Warehouse
The moment you have access to the API, you should start storing everything. Sixteen months sounds like enough until it isn't. I've seen teams want to compare Q4 2023 performance to Q4 2024, or analyze how a rebranding affected search visibility over two years. Without historical data, you can't do that.
Set up a simple PostgreSQL schema to store query-level data:
CREATE TABLE search_console_data (
id BIGSERIAL PRIMARY KEY,
property_url TEXT NOT NULL,
query_date DATE NOT NULL,
query TEXT NOT NULL,
page TEXT,
country TEXT DEFAULT 'all',
device TEXT DEFAULT 'all',
clicks INT DEFAULT 0,
impressions INT DEFAULT 0,
ctr DECIMAL(5, 4),
position DECIMAL(5, 2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(property_url, query_date, query, page, country, device)
);
CREATE INDEX idx_query_date ON search_console_data(property_url, query_date);
CREATE INDEX idx_query ON search_console_data(query);
CREATE INDEX idx_page ON search_console_data(page);
Once a day (or as often as your rate limits allow), fetch the latest data and upsert it into this table. This gives you a complete historical record. After a year, you'll have data spanning multiple calendar years, seasonal cycles, and algorithm updates. You can then compare trends, detect when queries stopped appearing, or identify rising opportunities that are too new for the Search Console UI to highlight effectively.
A practical workflow: every morning at 6 AM, a scheduled Lambda or cron job pulls the last 3 days of data from the Search Console API (to capture any late-arriving data from Google's side) and merges it into your database. Within a few weeks, you'll have more historical depth than the UI offers.
Group by Template — Segment Performance by Content Type
Raw query data is interesting, but it's more actionable when you group it by content type or template. Your blog posts, product pages, and category pages are probably optimized differently and rank for different query intent. Measuring them together obscures which content type is actually winning.
Add a template_type column to your schema and populate it based on URL patterns:
CREATE TABLE search_console_data (
id BIGSERIAL PRIMARY KEY,
property_url TEXT NOT NULL,
query_date DATE NOT NULL,
query TEXT NOT NULL,
page TEXT,
template_type TEXT, -- 'blog', 'product', 'category', etc.
country TEXT DEFAULT 'all',
device TEXT DEFAULT 'all',
clicks INT DEFAULT 0,
impressions INT DEFAULT 0,
ctr DECIMAL(5, 4),
position DECIMAL(5, 2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(property_url, query_date, query, page, country, device)
);
Before inserting data, classify each page URL:
function classifyPage(pageUrl) {
if (pageUrl.includes('/blog/')) return 'blog';
if (pageUrl.includes('/products/')) return 'product';
if (pageUrl.includes('/category/')) return 'category';
if (pageUrl === '/' || pageUrl === '') return 'homepage';
return 'other';
}
Now you can run queries like: "Which template type has the highest average CTR?" or "Did our product pages gain or lose impressions month-over-month?" Segmentation reveals patterns that aggregate data hides. You might discover your blog content is underperforming compared to product pages, or that category pages are bringing in volume but with zero clicks because they're ranking at position 8.
Automate Reports — Turn Data Into Action
Dashboards are nice, but reports that land in your inbox are better because they force action. Once a week, generate a summary of winners (queries with rising clicks), losers (queries that dropped), and newcomers (queries that appeared in the top 25 for the first time).
Here's a practical structure for a weekly report query:
-- Winners: queries with +20% clicks vs. last week
SELECT
query,
template_type,
SUM(CASE WHEN query_date >= NOW()::DATE - 7 THEN clicks ELSE 0 END) as this_week_clicks,
SUM(CASE WHEN query_date >= NOW()::DATE - 14 AND query_date < NOW()::DATE - 7 THEN clicks ELSE 0 END) as last_week_clicks,
ROUND(
100.0 * (
SUM(CASE WHEN query_date >= NOW()::DATE - 7 THEN clicks ELSE 0 END) -
SUM(CASE WHEN query_date >= NOW()::DATE - 14 AND query_date < NOW()::DATE - 7 THEN clicks ELSE 0 END)
) / NULLIF(SUM(CASE WHEN query_date >= NOW()::DATE - 14 AND query_date < NOW()::DATE - 7 THEN clicks ELSE 0 END), 0),
2
) as pct_change
FROM search_console_data
WHERE property_url = 'sc-domain:example.com'
AND query_date >= NOW()::DATE - 14
GROUP BY query, template_type
HAVING SUM(CASE WHEN query_date >= NOW()::DATE - 7 THEN clicks ELSE 0 END) > 5
ORDER BY pct_change DESC;
Format the results into an HTML email with sections for each template type. Include context: is the query new, or have we been ranking for it for months? Are we ranked in the top 3, or are we at position 8 with low CTR? New opportunities (queries that just appeared) are worth investigating—they might represent emerging demand or a recent piece of content starting to rank.
I automate this weekly report using a combination of a database query, a Lambda function, and SES for email delivery. The report takes 30 seconds to generate and often surfaces a query that's worth optimizing for within the week. That's leverage.
Common Mistakes
- Fetching all dimensions at once. The API limits how many rows you can retrieve. Fetch by query, then by page, then slice by date. Don't try to get everything in one call.
- Ignoring the 16-month window. You can't backfill data older than 16 months. Start collecting now, even if your data gap is incomplete. Better to have 18 months than to have wished you'd started 12 months ago.
- Treating the API as real-time. Google's data has a lag. Yesterday's data isn't complete until the next day. Allow 24 hours before reporting on it.
- Not handling duplicates. If your scheduled job crashes and reruns, you'll insert the same data twice. Use UPSERT (ON CONFLICT) to prevent duplicates.
- Forgetting to track template type at insert time. It's tempting to classify URLs after the fact, but if your URL structure changes, you'll lose consistency. Classify before storage.
Key Takeaways
- The Search Console API exposes clicks, impressions, and position data; use it to build your own historical archive beyond the 16-month UI limit.
- Store all data in a database with proper indexing; within months you'll have trends and patterns the UI can't show.
- Classify pages by template type before storage so you can segment performance by content type.
- Automate weekly reports highlighting winners, losers, and new queries; this keeps SEO insights in front of the team that can act on them.
- Start collecting data immediately; the sooner you begin, the richer your historical picture.






Comments
Loading comments…