Web & Technology  /  18 septiembre 2026

How to Create an Automated Google Search Console Report with Apps Script

Summarise with AI

Automate your Google Search Console analysis without spending a cent. This script extracts the URLs and keywords with the most clicks, compares the current period with the previous one, and sends the summary to your email — all with Google Apps Script.

Why Google Apps Script to automate GSC?

Google Apps Script is Google’s native automation platform. It runs in the cloud, integrates with GSC, Gmail and Drive, and is completely free.

  • No own server — you don’t need hosting or additional infrastructure
  • Data stays inside Google’s ecosystem — no third parties, no privacy issues
  • Works while you sleep — automatic daily or weekly trigger
  • Zero monthly cost — unlike tools such as SEMrush or Ahrefs

If you have an active SEO strategy, this script gives you daily visibility into which keywords and pages are gaining or losing positions — without opening the browser every morning.

Step 1: Create the project in Apps Script

  1. Go to script.google.com
  2. Click New project
  3. You will see a Code.gs file with an empty function
  4. Click Save (💾) and name it: GSC Automated Reports

Step 2: Enable the Search Console API

Apps Script does not include GSC pre-installed — you need to enable it manually in Google Cloud:

  1. Go to console.cloud.google.com
  2. Select or create a project
  3. Go to APIs & Services → Library
  4. Search for Google Search Console API and click Enable

Then link the Cloud project to your Apps Script:

  1. Back in Apps Script → Project Settings (⚙️)
  2. Under Google Cloud Platform (GCP) Project, click Change project
  3. Enter the Cloud Console project number and save

Step 3: The complete script

Select all the content of Code.gs and replace it with this code:

// ============================================
// CONFIGURATION — EDIT THESE VALUES
// ============================================
const CONFIG = {
  siteUrl: 'https://your-domain.com/',
  emailTo: 'your-email@gmail.com',
  frequency: 'daily',
  daysToAnalyze: 7,
  topResults: 10
};

function runGSCReport() {
  try {
    const report = generateReport();
    sendEmail(report);
    Logger.log('Report sent successfully');
  } catch (error) {
    Logger.log('Error: ' + error.toString());
    MailApp.sendEmail({ to: CONFIG.emailTo, subject: '❌ Error in GSC Report', body: error.toString() });
  }
}

function generateReport() {
  const endDate   = new Date();
  const startDate = new Date();
  startDate.setDate(endDate.getDate() - CONFIG.daysToAnalyze);
  const startStr    = formatDate(startDate);
  const endStr      = formatDate(endDate);
  const currentData = fetchGSCData(startStr, endStr);
  const prevEndDate   = new Date(startDate);
  const prevStartDate = new Date(prevEndDate);
  prevStartDate.setDate(prevEndDate.getDate() - CONFIG.daysToAnalyze);
  const prevData = fetchGSCData(formatDate(prevStartDate), formatDate(prevEndDate));
  return {
    current: currentData, previous: prevData,
    period: startStr + ' to ' + endStr,
    prevPeriod: formatDate(prevStartDate) + ' to ' + formatDate(prevEndDate)
  };
}

function fetchGSCData(startDate, endDate) {
  const url = 'https://www.googleapis.com/webmasters/v3/sites/' +
              encodeURIComponent(CONFIG.siteUrl) + '/searchAnalytics/query';
  const payload = { startDate, endDate, dimensions: ['query', 'page'], rowLimit: CONFIG.topResults * 2 };
  const options = {
    method: 'post', contentType: 'application/json',
    payload: JSON.stringify(payload), muteHttpExceptions: true,
    headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() }
  };
  const response = UrlFetchApp.fetch(url, options);
  if (response.getResponseCode() !== 200) throw new Error('GSC API Error: ' + response.getContentText());
  return JSON.parse(response.getContentText());
}

function formatDate(date) {
  return Utilities.formatDate(date, Session.getScriptTimeZone(), 'yyyy-MM-dd');
}

function sendEmail(report) {
  const currentRows = report.current.rows  || [];
  const prevRows    = report.previous.rows || [];
  let cClicks = 0, cImpr = 0, pClicks = 0, pImpr = 0;
  currentRows.forEach(r => { cClicks += r.clicks||0; cImpr += r.impressions||0; });
  prevRows.forEach(r    => { pClicks += r.clicks||0; pImpr += r.impressions||0; });
  const chg  = pClicks > 0 ? ((cClicks - pClicks) / pClicks * 100).toFixed(1) : 0;
  const ichg = pImpr   > 0 ? ((cImpr   - pImpr)   / pImpr   * 100).toFixed(1) : 0;

  let html = `<h2>📊 Google Search Console Report</h2>
    <p><strong>Site:</strong> ${CONFIG.siteUrl} | <strong>Period:</strong> ${report.period}</p>
    <h3>📈 Summary</h3>
    <table border="1" cellpadding="8" style="border-collapse:collapse;">
      <tr style="background:#f0f0f0;"><th>Metric</th><th>Current</th><th>Previous</th><th>Change</th></tr>
      <tr><td>Clicks</td><td>${cClicks}</td><td>${pClicks}</td>
          <td style="color:${chg>=0?'green':'red'}">${chg>=0?'+':''}${chg}%</td></tr>
      <tr><td>Impressions</td><td>${cImpr}</td><td>${pImpr}</td>
          <td style="color:${ichg>=0?'green':'red'}">${ichg>=0?'+':''}${ichg}%</td></tr>
    </table>`;

  const urlMap = new Map();
  currentRows.forEach(r => { const p=r.keys[1]; if(!urlMap.has(p)) urlMap.set(p,r); });
  html += `<h3>🔍 Top URLs</h3><table border="1" cellpadding="8" style="border-collapse:collapse;">
    <tr style="background:#f0f0f0;"><th>URL</th><th>Clicks</th><th>Impressions</th><th>CTR</th><th>Pos</th></tr>`;
  let uc=0;
  urlMap.forEach((r,page)=>{ if(uc++>=CONFIG.topResults) return;
    const ctr=r.impressions>0?((r.clicks/r.impressions)*100).toFixed(2):0;
    html+=`<tr><td>${page}</td><td>${r.clicks}</td><td>${r.impressions}</td><td>${ctr}%</td><td>${r.position?.toFixed(1)||'-'}</td></tr>`;
  });
  html += `</table>`;

  const kwMap = new Map();
  currentRows.forEach(r => { const q=r.keys[0]; if(!kwMap.has(q)) kwMap.set(q,r); });
  html += `<h3>🎯 Top Keywords</h3><table border="1" cellpadding="8" style="border-collapse:collapse;">
    <tr style="background:#f0f0f0;"><th>Keyword</th><th>Clicks</th><th>Impressions</th><th>CTR</th><th>Pos</th></tr>`;
  let kc=0;
  kwMap.forEach((r,q)=>{ if(kc++>=CONFIG.topResults) return;
    const ctr=r.impressions>0?((r.clicks/r.impressions)*100).toFixed(2):0;
    html+=`<tr><td>${q}</td><td>${r.clicks}</td><td>${r.impressions}</td><td>${ctr}%</td><td>${r.position?.toFixed(1)||'-'}</td></tr>`;
  });
  html += `</table><hr><p style="font-size:12px;color:#666;">Generated by GSC Automated Reports</p>`;

  MailApp.sendEmail({ to: CONFIG.emailTo, subject: `📊 GSC Report: ${CONFIG.siteUrl} | ${report.period}`, htmlBody: html, name: 'GSC Report Bot' });
}

function setupTrigger() {
  ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t));
  if (CONFIG.frequency === 'daily') {
    ScriptApp.newTrigger('runGSCReport').timeBased().everyDays(1).atHour(8).nearMinute(0).create();
  } else {
    ScriptApp.newTrigger('runGSCReport').timeBased().onWeekDay(ScriptApp.WeekDay.MONDAY).atHour(8).create();
  }
  runGSCReport();
}

Step 4: Configure the Manifest file

  1. Go to Project Settings (⚙️)
  2. Enable «Show the appsscript.json manifest file in editor»
  3. Open appsscript.json and paste:
{
  "timeZone": "Europe/London",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/webmasters.readonly",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/script.send_mail",
    "https://www.googleapis.com/auth/script.scriptapp"
  ]
}

Save with Ctrl + S.

Step 5: Run and authorise

  1. Edit the CONFIG section at the top with your domain and email
  2. In the function dropdown, select setupTrigger
  3. Click Run (▶️)

The Google authorisation screen will appear:

  • Click Review permissions → select your account
  • On the «Google hasn’t verified this app» screen: Advanced → Go to [name] (unsafe)
  • Tick all permissions and click Allow

This authorisation is one-time only. After that the script runs without any intervention.

Step 6: Verify the trigger

Go to Triggers (⏰ in the left panel) and check that it shows:

  • Function: runGSCReport
  • Event: Time-driven
  • Frequency: Day timer or Week timer

Check your email — the first test report should arrive within minutes.

What does the report include?

Section Content
Summary Total clicks and impressions vs previous period, with % change
Top URLs The 10 pages with the most clicks, with CTR and average position
Top Keywords The 10 keywords with the most clicks, with CTR and average position
Changes Green/red indicating rises or drops vs the previous period

The Top Keywords data is especially useful for spotting which terms deserve additional optimisation — the same analysis as our professional keyword research service.

Limitations to keep in mind

  • Data delay: GSC has a 2–3 day lag. You won’t see yesterday’s data.
  • Row limit: 25,000 per query. For large sites you need additional pagination.
  • Manual authorisation: the first time requires clicking «Allow». This cannot be avoided.

Need help setting it up?

At Brandgrowth we set up this type of automation for our SEO clients. If you’d prefer we do it for you — or integrate it into your technical SEO strategy — tell us about your project and we’ll get back to you in under 24 hours.

Julio Febres

Author

Julio Febres

Estrategia Digital — IA SEO & Diseño Web

Especialista en IA SEO y Diseño Web con más de 6 años de experiencia desde 2020. Trabajo directamente con marcas para que aparezcan en Google, AI Overviews y motores generativos como ChatGPT y Perplexity. Creador del canal @juliofebresSEO sobre SEO y estrategia digital.

Prefer to have it done for you?

Position your brand where your customers are searching

Talk to the team

Shall we talk about your project?

We reply in under 24 hours

Contact us