AutomationFlowsAI & RAG › Automate Monthly Google Ads Performance Analysis with Gpt-4o, Sheets & Slack

Automate Monthly Google Ads Performance Analysis with Gpt-4o, Sheets & Slack

ByNikan Noorafkan @nikkannoora on n8n.io

This workflow automatically analyzes Google Ads performance every month, using the Google Ads API and OpenAI (GPT-4o) to uncover which ad themes, categories, and messages perform best. It then generates a structured AI report, saves it to Google Sheets, and sends a Slack summary…

Cron / scheduled trigger★★★★☆ complexityAI-powered13 nodesHTTP RequestAgentOpenAI ChatGoogle SheetsSlack
AI & RAG Trigger: Cron / scheduled Nodes: 13 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #10062 — we link there as the canonical source.

This workflow follows the Agent → Google Sheets recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "nodes": [
    {
      "id": "9c5d8412-ea52-4af7-a129-f3808418de5c",
      "name": "Monthly Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "notes": "Runs 1st of every month to analyze last 30 days",
      "position": [
        -140,
        -60
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 0 1 * *"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "69c788b5-fdc2-4407-86c2-13dffe38bf61",
      "name": "Get Performance Data",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Gets 30-day performance data via GAQL. Min 100 impressions for statistical validity.",
      "position": [
        140,
        -60
      ],
      "parameters": {
        "url": "=https://googleads.googleapis.com/{{$env.GOOGLE_ADS_API_VERSION}}/customers/{{$env.GOOGLE_ADS_CUSTOMER_ID}}/googleAds:search",
        "method": "POST",
        "options": {},
        "jsonBody": "={\"query\": \"SELECT ad_group_ad.ad.id, ad_group_ad.ad.responsive_search_ad.headlines, ad_group.name, metrics.impressions, metrics.clicks, metrics.ctr FROM ad_group_ad WHERE segments.date DURING LAST_30_DAYS AND metrics.impressions > 100 ORDER BY metrics.clicks DESC LIMIT 1000\"}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleAdsOAuth2Api"
      },
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "fb6f2b02-9ca1-4260-a761-d1fa23b6e926",
      "name": "Prepare Performance Data",
      "type": "n8n-nodes-base.code",
      "notes": "Analyzes performance data, groups by category/theme, calculates CTRs, formats for AI",
      "position": [
        520,
        -60
      ],
      "parameters": {
        "jsCode": "// Format performance data for AI analysis\n\nconst response = $input.first().json;\nconst results = response.results || [];\n\n// Group by category (ad group) and extract headlines\nconst categoryData = {};\nconst themeData = {};\n\nresults.forEach(result => {\n  const category = result.adGroup?.name || 'Unknown';\n  const headlines = result.adGroupAd?.ad?.responsiveSearchAd?.headlines || [];\n  const headline = headlines[0]?.text || '';\n  const ctr = parseFloat(result.metrics?.ctr || 0);\n  const impressions = parseInt(result.metrics?.impressions || 0);\n  const clicks = parseInt(result.metrics?.clicks || 0);\n  \n  if (impressions < 100) return;\n  \n  // Aggregate by category\n  if (!categoryData[category]) {\n    categoryData[category] = {impressions: 0, clicks: 0, ads: []};\n  }\n  categoryData[category].impressions += impressions;\n  categoryData[category].clicks += clicks;\n  categoryData[category].ads.push({headline, ctr, impressions, clicks});\n  \n  // Track themes\n  const themes = ['vegan', 'organic', 'natural', 'premium', 'sale', 'new', 'free shipping'];\n  themes.forEach(theme => {\n    if (headline.toLowerCase().includes(theme)) {\n      if (!themeData[theme]) {\n        themeData[theme] = {impressions: 0, clicks: 0, count: 0};\n      }\n      themeData[theme].impressions += impressions;\n      themeData[theme].clicks += clicks;\n      themeData[theme].count += 1;\n    }\n  });\n});\n\n// Calculate CTRs\nconst categoryAnalysis = Object.entries(categoryData).map(([cat, data]) => ({\n  category: cat,\n  ctr: ((data.clicks / data.impressions) * 100).toFixed(2),\n  total_impressions: data.impressions,\n  total_clicks: data.clicks,\n  ad_count: data.ads.length\n})).sort((a, b) => parseFloat(b.ctr) - parseFloat(a.ctr));\n\nconst themeAnalysis = Object.entries(themeData).map(([theme, data]) => ({\n  theme,\n  ctr: ((data.clicks / data.impressions) * 100).toFixed(2),\n  total_impressions: data.impressions,\n  ad_count: data.count\n})).sort((a, b) => parseFloat(b.ctr) - parseFloat(a.ctr));\n\nreturn {\n  total_ads_analyzed: results.length,\n  date_range: 'Last 30 days',\n  top_categories: categoryAnalysis.slice(0, 5),\n  bottom_categories: categoryAnalysis.slice(-5),\n  top_themes: themeAnalysis.slice(0, 5),\n  bottom_themes: themeAnalysis.slice(-5),\n  analysis_prompt: `Analyze this Google Ads performance data and provide actionable insights:\\n\\nTop Performing Categories:\\n${JSON.stringify(categoryAnalysis.slice(0, 5), null, 2)}\\n\\nBottom Performing Categories:\\n${JSON.stringify(categoryAnalysis.slice(-5), null, 2)}\\n\\nTop Performing Themes:\\n${JSON.stringify(themeAnalysis.slice(0, 5), null, 2)}\\n\\nBottom Performing Themes:\\n${JSON.stringify(themeAnalysis.slice(-5), null, 2)}\\n\\nProvide 3-5 specific recommendations for improving ad copy. Include estimated impact percentages. Format as: {'recommendations': [{'action': '...', 'expected_impact': '...', 'priority': 'HIGH/MEDIUM/LOW'}]}`\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "15384935-f99f-4e06-a475-b232106fe256",
      "name": "AI Agent - Analyze Performance",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "notes": "n8n AI Agent analyzes patterns and provides insights",
      "position": [
        800,
        -60
      ],
      "parameters": {
        "text": "={{$json.analysis_prompt}}",
        "options": {
          "systemMessage": "You are a Google Ads performance analyst. Analyze the data and provide specific, actionable recommendations. Return valid JSON only."
        },
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "9adc1284-fa91-43c3-b326-260a51cfb000",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "notes": "Lower temperature (0.2) for analytical tasks",
      "position": [
        780,
        120
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {
          "maxTokens": 2000,
          "temperature": 0.2
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e5f12de9-b23c-4052-872e-466a9148b404",
      "name": "Generate Report",
      "type": "n8n-nodes-base.code",
      "notes": "Creates formatted report with AI insights",
      "position": [
        1260,
        -60
      ],
      "parameters": {
        "jsCode": "// Parse AI recommendations and create report\n\nconst aiOutput = $input.first().json.output;\nconst performanceData = $node['Prepare Performance Data'].json;\n\n// Parse JSON from AI\nlet recommendations;\ntry {\n  const jsonMatch = aiOutput.match(/```json\\s*([\\s\\S]*?)```/) || aiOutput.match(/{[\\s\\S]*}/);\n  recommendations = JSON.parse(jsonMatch ? jsonMatch[1] || jsonMatch[0] : aiOutput);\n} catch (e) {\n  recommendations = {recommendations: [{action: aiOutput, priority: 'HIGH'}]};\n}\n\nconst report = `# 30-Day Performance Analysis Report\n\n## Executive Summary\nAnalyzed: ${performanceData.total_ads_analyzed} ads\nPeriod: ${performanceData.date_range}\nDate: ${new Date().toISOString().split('T')[0]}\n\n## Top Performing Categories\n${performanceData.top_categories.map(c => `- ${c.category}: ${c.ctr}% CTR (${c.ad_count} ads)`).join('\\n')}\n\n## Top Performing Themes\n${performanceData.top_themes.map(t => `- \"${t.theme}\" messaging: ${t.ctr}% CTR (${t.ad_count} ads)`).join('\\n')}\n\n## AI-Powered Recommendations\n${recommendations.recommendations?.map((r, i) => `${i+1}. [${r.priority}] ${r.action}\\n   Expected Impact: ${r.expected_impact || 'TBD'}`).join('\\n\\n')}\n\n## Next Steps\n1. Update ad copy to emphasize top-performing themes\n2. A/B test recommendations\n3. Review again in 30 days\n\n---\nGenerated by n8n AI Agent + OpenAI\n`;\n\nreturn {\n  report_markdown: report,\n  recommendations: recommendations.recommendations || [],\n  top_themes: performanceData.top_themes,\n  generated_at: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "ef39ae9e-d5f1-4ac5-83d2-648d70637564",
      "name": "Save Report to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "notes": "Archives report in Google Sheets",
      "position": [
        1500,
        -60
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Performance Reports"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{$env.GOOGLE_SHEET_ID}}"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "875c5cb7-e6d2-4576-89ad-334e88668200",
      "name": "Send Report",
      "type": "n8n-nodes-base.slack",
      "notes": "Sends report to team via Slack",
      "position": [
        1720,
        -60
      ],
      "parameters": {
        "text": "={{$json.report_markdown}}",
        "otherOptions": {}
      },
      "typeVersion": 2.1
    },
    {
      "id": "77996afd-b9cb-49d1-a0b4-996210484fa6",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -260,
        -360
      ],
      "parameters": {
        "width": 280,
        "height": 220,
        "content": "## \ud83d\udfe8 1 \u201cMonthly Trigger\u201d\n\n\ud83d\udd53 Runs every month\nAutomatically starts on the 1st of each month to analyze the last 30 days of Google Ads performance."
      },
      "typeVersion": 1
    },
    {
      "id": "7175f013-792a-4e70-80d3-035ec5cd485c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -40,
        140
      ],
      "parameters": {
        "width": 360,
        "height": 220,
        "content": "## \ud83d\udfe8 2 \u201cGet Performance Data\u201d\n\n\ud83d\udcca Fetches ad metrics\nQueries Google Ads API for CTR, clicks, and impressions using GAQL.\nFilters out ads with fewer than 100 impressions for accurate analysis."
      },
      "typeVersion": 1
    },
    {
      "id": "439ff524-d0ad-4a84-b697-be637f70447f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        380,
        -400
      ],
      "parameters": {
        "width": 420,
        "height": 200,
        "content": "## \ud83d\udfe8 3 \u201cPrepare Performance Data\u201d\n\n\ud83e\uddee Processes raw metrics\nGroups ads by category and common themes (e.g., \u201csale,\u201d \u201cfree shipping\u201d).\nCalculates average CTRs and builds a summary prompt for AI analysis."
      },
      "typeVersion": 1
    },
    {
      "id": "16c3d3b7-7f19-4c31-8b72-161f8d7c5b8c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        740,
        260
      ],
      "parameters": {
        "width": 360,
        "height": 220,
        "content": "## \ud83d\udfe8 4 \u201cAI Agent - Analyze Performance\u201d\n\n\ud83e\udd16 AI insight generation\nSends the summary data to GPT-4o for analysis.\nThe AI recommends optimizations and highlights top-performing messaging."
      },
      "typeVersion": 1
    },
    {
      "id": "fb0e5d69-95b4-4cd4-834a-b5f563fb367a",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1080,
        -400
      ],
      "parameters": {
        "width": 420,
        "height": 200,
        "content": "## \ud83d\udfe8 5 \u201cGenerate Report\u201d\n\n\ud83e\uddfe Creates readable summary\nConverts AI output into a formatted markdown report.\nIncludes actionable recommendations and key performance stats."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Generate Report": {
      "main": [
        [
          {
            "node": "Save Report to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Monthly Trigger": {
      "main": [
        [
          {
            "node": "Get Performance Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Analyze Performance",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get Performance Data": {
      "main": [
        [
          {
            "node": "Prepare Performance Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Report to Sheets": {
      "main": [
        [
          {
            "node": "Send Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Performance Data": {
      "main": [
        [
          {
            "node": "AI Agent - Analyze Performance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Analyze Performance": {
      "main": [
        [
          {
            "node": "Generate Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow automatically analyzes Google Ads performance every month, using the Google Ads API and OpenAI (GPT-4o) to uncover which ad themes, categories, and messages perform best. It then generates a structured AI report, saves it to Google Sheets, and sends a Slack summary…

Source: https://n8n.io/workflows/10062/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Created by: Peyton Leveillee Last updated: October 2025

OpenAI Chat, Google Sheets, HTTP Request +5
AI & RAG

Marketing, content, and enablement teams that need a quick, human-readable summary of every new video published by the YouTube channels they care about—without leaving Slack.

HTTP Request, Google Sheets, XML +7
AI & RAG

This workflow automates end-to-end ESG (Environmental, Social, and Governance) sustainability reporting for enterprise sustainability teams, compliance officers, and green governance leads. It solves

Agent, OpenAI Chat, Output Parser Structured +12
AI & RAG

Automates sales data analysis and strategic insight generation for sales managers and strategists needing actionable intelligence. Fetches multi-source data from sales, marketing, and financial system

HTTP Request, Agent, OpenAI Chat +6
AI & RAG

Scheduled triggers run automated price checks across multiple travel data sources. The collected data is aggregated, validated, and processed through an AI analysis layer that compares trends, detects

HTTP Request, OpenAI Chat, Agent +4