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 →
{
"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.
googleAdsOAuth2ApiopenAiApi
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Created by: Peyton Leveillee Last updated: October 2025
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.
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
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
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