Automation

How to automate SEO reporting for free (and what to keep manual)

Automate a monthly SEO report with Data Studio's scheduled email or an Apps Script that logs Search Console and GA4 data, and know what still needs you.

// in this article

To automate SEO reporting for free, connect Search Console and Google Analytics 4 to a one-page Data Studio report and use its Schedule delivery option to email it as a PDF every month. If you also want a permanent record of the numbers, add a short Apps Script to a Google Sheet that pulls last month’s totals on the 4th of each month.

That covers the copy-and-paste work, which is most of the time a report takes. What it can’t do is explain why the numbers moved, so the last step stays manual: a three-line note on top. This guide shows both free setups, when a paid tool is worth it, and the limits that break automated reports. Every limit and menu name below was checked against Google’s documentation in November 2026.

What to automate and what to keep

Automate anything that is the same every month: pulling numbers, building charts, comparing periods, and sending the email. Keep anything that needs judgment: the summary, the reason a page dropped, and what you need from the client.

step_01 Pull data Search Console and GA4 connectors. automatic
step_02 Build page Scorecards with a comparison period. automatic
step_03 Send Scheduled PDF email on the 4th. automatic
step_04 Write the note What moved, why, what's next.
fig 1 · three steps a machine can run every month, and the one you still write yourself

If you don’t have a report structure yet, start with our monthly SEO reporting template. It lists seven sections and which numbers belong in each. The setups below fill that template without you opening a single dashboard.

Three ways to automate it, compared

Data Studio scheduled email Google Sheet with Apps Script Paid reporting tool
Cost Free Free From about $20 per client a month
Code needed None About 40 lines, copy and paste None
Delivery PDF and link by email A new row in a sheet, optional email Email, client portal, white label
Keeps data past 16 months No Yes Depends on the tool
Best for One site or a few clients Keeping a history, custom numbers Ten or more clients

The 16-month column matters more than it looks. Google says Search Console keeps data for the last 16 months, so a Data Studio report can’t show a year-on-year comparison for a month older than that. A sheet that saves one row a month keeps it forever.

The paid price is AgencyAnalytics’ current Core plan: $20 per client per month, billed annually, with a 14-day free trial. Other tools price differently, so check before you commit.

Method 1: a Data Studio report that emails itself

Data Studio is the free dashboard tool Google used to call Looker Studio. Its scheduling feature sends a report on a timer, which is all most small businesses need.

  1. Connect Search Console. In Data Studio, create a data source, pick the Search Console connector, choose your site and pick a table. Google’s connector guide explains that a data source can use either Site Impression or URL Impression, not both, so add two data sources if you want sitewide totals and a page list.
  2. Connect GA4. Add a second data source with the Google Analytics connector. You need “Read & Analyze” permission on the property.
  3. Build one page. Scorecards for organic key events, clicks and impressions, a non-branded clicks scorecard with a filter that excludes queries containing your brand name, and a table of the top pages. Fewer charts make a better PDF.
  4. Set the dates. Set the default date range to the previous calendar month, not a rolling 28 days, so each email covers one clean month. Then set a Comparison date range of Previous period or Previous year on each scorecard, as Google’s date range guide shows.
  5. Schedule it. Click Share, then Schedule delivery. Add recipients, pick the pages, set a start date and time, and choose how often it repeats.

Step 4 matters most. With a rolling date range, the email that lands on the 4th covers part of last month and part of this one, and the comparison shifts every time. The numbers will look wrong to anyone who checks them against Search Console.

What the scheduled email contains

According to Google’s scheduling documentation, each email has the report as a PDF attachment, a preview of the first page and a link to the live report. You can send it to up to 50 addresses or group aliases, and you are always included.

inbox: monthly seo report
Monthly SEO report: Tri-County Plumbing from Data Studio · 4th of the month, 7:00 am preview: page 1 of the report link: view the full report attachment: report.pdf
fig 2 · what a scheduled data studio email contains, for a made-up plumber, based on google's scheduling docs

The limits of the free version

  • One schedule per report. Without the paid Pro tier you get one schedule per report. Pro allows up to 200, plus a custom subject and message, “Send now”, and delivery to Google Chat and Slack.
  • Email quotas. Personal accounts can send 200 emails a day and 800 a month. Google Workspace accounts get 500 a day and 3,000 a month. An agency with 30 clients on one personal account is nowhere near that, but a weekly schedule to big group aliases can be.
  • Time zone. Delivery follows your computer’s operating system time zone, and you can’t change it in Data Studio. If you work from California and your client is in New York, the 7 am email lands at 10 am their time.
  • No note. Without Pro you can’t write a custom message, so the email arrives as a bare PDF. Send your summary as a separate reply to the same thread.

Schedule the email for the 4th of the month or later. Google says Search Console data is normally available in 2 to 3 days, so a report sent on the 1st will be missing the last days of the month.

Method 2: a Google Sheet that logs your numbers every month

A sheet suits you if you want a history that outlives Search Console’s 16 months, or numbers Data Studio can’t easily show side by side. The script below runs once a month, pulls last month’s Search Console totals and organic GA4 sessions and key events, and adds one row to a sheet.

  1. Create the sheet. Make a Google Sheet with a tab called Monthly, then open Extensions, then Apps Script.
  2. Add the GA4 service. In the editor, next to Services, click Add a service and pick Analytics Data. It wraps the Google Analytics Data API.
  3. Switch to a standard Cloud project and enable the APIs. Apps Script can only call the Search Console API if the API is turned on in the script’s Cloud project, and Google’s Cloud project guide says you can’t change APIs in the default project. In Project Settings, change the project to a standard one, then enable the Google Search Console API and the Google Analytics Data API in the Cloud console. You will also be asked to set up an OAuth consent screen there.
  4. Set the scopes. In Project Settings, tick the box to show appsscript.json, then add the scopes below. Google’s scopes guide explains the oauthScopes field.
  5. Paste the script, run createTrigger once, and approve access.

The scopes block for appsscript.json:

"oauthScopes": [
  "https://www.googleapis.com/auth/webmasters.readonly",
  "https://www.googleapis.com/auth/analytics.readonly",
  "https://www.googleapis.com/auth/spreadsheets.currentonly",
  "https://www.googleapis.com/auth/script.external_request",
  "https://www.googleapis.com/auth/script.scriptapp"
]

The script. Change the two values at the top:

const SITE = 'sc-domain:example.com'; // or 'https://www.example.com/'
const GA4_PROPERTY = '123456789';     // your GA4 property ID

function monthlyReport() {
  const tz = Session.getScriptTimeZone();
  const now = new Date();
  const first = new Date(now.getFullYear(), now.getMonth() - 1, 1);
  const last = new Date(now.getFullYear(), now.getMonth(), 0);
  const start = Utilities.formatDate(first, tz, 'yyyy-MM-dd');
  const end = Utilities.formatDate(last, tz, 'yyyy-MM-dd');

  // Search Console: no dimensions returns one row of totals.
  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: start, endDate: end, type: 'web' }),
  });
  const gsc = (JSON.parse(res.getContentText()).rows || [{}])[0];

  // GA4: sessions and key events by channel, keep Organic Search.
  const report = AnalyticsData.Properties.runReport({
    dimensions: [{ name: 'sessionDefaultChannelGroup' }],
    metrics: [{ name: 'sessions' }, { name: 'keyEvents' }],
    dateRanges: [{ startDate: start, endDate: end }],
  }, 'properties/' + GA4_PROPERTY);
  const organic = (report.rows || []).find(
    (r) => r.dimensionValues[0].value === 'Organic Search');

  SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Monthly').appendRow([
    Utilities.formatDate(first, tz, 'yyyy-MM'),
    gsc.clicks || 0,
    gsc.impressions || 0,
    organic ? Number(organic.metricValues[0].value) : 0,
    organic ? Number(organic.metricValues[1].value) : 0,
  ]);
}

function createTrigger() {
  ScriptApp.newTrigger('monthlyReport').timeBased().onMonthDay(4).atHour(6).create();
}

Every name in that script comes from Google’s reference pages: the searchAnalytics.query endpoint and its type field, the GA4 API schema for sessionDefaultChannelGroup, sessions and keyEvents, and the clock trigger builder for onMonthDay and atHour. Leaving out dimensions in the Search Console request returns a single row of totals for the whole month.

Run monthlyReport once by hand to check it adds a row, then run createTrigger. Google notes that a trigger set for 6 am may fire any time between 6 and 7 am, which doesn’t matter for a monthly job. You can also make the trigger without code: click Triggers on the left, then Add Trigger.

tab: Monthly month · clicks · impr · sessions · key 2026-08 · 1,240 · 41,300 · 1,105 · 31 2026-09 · 1,310 · 44,900 · 1,182 · 36 2026-10 · 1,420 · 47,200 · 1,263 · 39
trigger: month timer function: monthlyReport day 4, between 6 and 7 am adds one row per month, forever
fig 3 · the sheet after three runs, for a made-up site. Illustrative, not measured data.

Once the sheet has a few rows, point a Data Studio chart at it. You get a long-run trend line that keeps going after Search Console has dropped the old months.

Where the script can break

  • Wrong property format. A Domain property needs sc-domain: in front. A URL-prefix property needs the full URL with the trailing slash, exactly as Search Console shows it.
  • Quotas. Apps Script allows 20 triggers per user per script and 6 minutes per run. One monthly call per site is far inside both, but a single script looping over 200 client sites could run out of time.
  • GA4 limits. A standard property gets 200,000 core tokens a day. One small report a month uses almost none, but Data Studio dashboards on the same property draw from the same pool.
  • Silent zeros. If someone removes the GA4 tag from the site, the script still adds a row, full of zeros. That is why you read the numbers before you send anything.

When a paid tool is worth it

Free setups get slow when you manage many clients. Each Data Studio report has to be copied and reconnected per client, and the free email has no custom message. Paid tools add client portals, white-label branding, rank tracking and Google Business Profile data in one place.

A rough test: if building and checking reports takes you more than a day a month, a tool at $20 per client is cheaper than your time. Below about ten clients, the free route usually wins. For local clients you can also pull calls and direction requests yourself with the Google Business Profile API, though access takes an application and a profile that’s at least 60 days old.

The part you shouldn’t automate

An automated report answers “what happened”. The client wants to know “is it working, and what are you doing about it”. Those answers need someone who knows that the drop in October was the site migration, or that the jump came from a radio ad and not from SEO.

✕ Automated only
clicks: 1,420 (+8%)
key events: 39 (+8%)
attachment: report.pdf
✓ Automated plus a note
39 leads from search, up from 36
gain came from the new drain page
next: 2 service pages, need photos
fig 4 · the same made-up month sent as a bare pdf and with a three-line note. Illustrative, not measured data.

Spend five minutes a month on that note. Check the numbers look sane first: a month of zeros means broken tracking, not a bad month. If Search Console and GA4 disagree by 10 or 20%, that is normal, and our guide to Search Console vs Google Analytics explains why so you can tell the client once and move on.

FAQ

Can SEO reporting be fully automated?

The data collection, charts and delivery can be. The explanation can’t, because a tool doesn’t know about the site redesign, the new competitor or the client’s radio campaign. Automate the numbers and write the summary yourself.

Is Data Studio free for automated reports?

Yes. Scheduled email delivery works on the free version, with one schedule per report and a maximum of one delivery a day. The paid Pro tier adds more schedules, custom messages and Slack delivery, according to Google’s docs.

How do I send automated SEO reports to clients?

Build the report in Data Studio, open Share, then Schedule delivery, and add the client’s email address. They get a PDF and a link each month. Give them view access to the report if you want the link to work without a sign-in request.

What is the best automated SEO reporting tool?

For one site or a handful of clients, Data Studio with the free Search Console and GA4 connectors is enough. Paid tools earn their cost when you manage many clients and need white-label reports or client portals.

The short version

Build one Data Studio page with a fixed last-month date range, schedule it for the 4th, and add a Sheets script if you want to keep your numbers past 16 months. Then spend the time you saved on the three sentences that tell the client what happened and what comes next. That note is the part they read.

Portrait of Liam Fraser
// written byLiam FraserSEO specialist with 3 years of experience. Liam writes about what works in local, technical and AI search.About SerpSurfers →