This workflow corresponds to n8n.io template #16306 — we link there as the canonical source.
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 →
{
"id": "Ajh0NO1jkk1UIlek",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Competitor Launch Calendar",
"tags": [],
"nodes": [
{
"id": "a0dd3bb9-36e8-4bea-9a7b-274639559d15",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
368,
272
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
1
]
}
]
}
},
"typeVersion": 1.3
},
{
"id": "a9fb44eb-4ade-450e-a0f9-4a2229ab3182",
"name": "Read Competitors",
"type": "n8n-nodes-base.googleSheets",
"position": [
592,
272
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AA9I5PPuJEMDyWOE_BroajTXaHGJdcjC6ucL5ZiEtNM/edit#gid=0",
"cachedResultName": "competitors"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1AA9I5PPuJEMDyWOE_BroajTXaHGJdcjC6ucL5ZiEtNM",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AA9I5PPuJEMDyWOE_BroajTXaHGJdcjC6ucL5ZiEtNM/edit?usp=drivesdk",
"cachedResultName": "Competitor Launch Calendar "
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "a9ab08bc-5fbe-45d4-8f36-88279c411f76",
"name": "Loop Over Competitors",
"type": "n8n-nodes-base.splitInBatches",
"position": [
800,
272
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "40105c0f-ed19-4661-ae4c-6c792ec9a4ec",
"name": "Search Facebook Ads",
"type": "n8n-nodes-adyntel.adyntel",
"position": [
1120,
80
],
"parameters": {
"companyDomain": "={{ $json.domain }}",
"requestOptions": {}
},
"credentials": {
"adyntelApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "63a622e0-f6ec-4540-9ca5-b88e4feff529",
"name": "Search Google Ads",
"type": "n8n-nodes-adyntel.adyntel",
"position": [
1120,
272
],
"parameters": {
"resource": "googleAds",
"companyDomain": "={{ $json.domain }}",
"requestOptions": {}
},
"credentials": {
"adyntelApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "13c298ad-a02d-4721-a96e-c03511360a81",
"name": "Search LinkedIn Ads",
"type": "n8n-nodes-adyntel.adyntel",
"position": [
1120,
464
],
"parameters": {
"resource": "linkedInAds",
"companyDomain": "={{ $json.domain }}",
"requestOptions": {}
},
"credentials": {
"adyntelApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "66eb97be-814d-4154-a657-023f59946794",
"name": "Merge All Platform Results",
"type": "n8n-nodes-base.merge",
"position": [
1328,
256
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition",
"numberInputs": 3
},
"typeVersion": 3.2
},
{
"id": "087ab401-667c-46a9-bb4a-d61d884295d5",
"name": "Extract Timeline",
"type": "n8n-nodes-base.code",
"position": [
1520,
272
],
"parameters": {
"jsCode": "function extractTitle(ad) {\n if (ad.headline && typeof ad.headline === 'object') {\n const t = ad.headline.title || ad.headline.description;\n if (t && !t.includes('{{')) return t;\n }\n if (ad.commentary && ad.commentary.text && !ad.commentary.text.includes('{{')) {\n return ad.commentary.text.substring(0, 80);\n }\n if (ad.snapshot) {\n if (Array.isArray(ad.snapshot.cards) && ad.snapshot.cards.length > 0) {\n for (const card of ad.snapshot.cards) {\n if (card.title && !card.title.includes('{{')) return card.title;\n if (card.body && typeof card.body === 'string' && !card.body.includes('{{')) return card.body.substring(0, 80);\n }\n }\n if (ad.snapshot.title && !ad.snapshot.title.includes('{{')) return ad.snapshot.title;\n if (ad.snapshot.body) {\n const bodyText = typeof ad.snapshot.body === 'object' ? ad.snapshot.body.text : ad.snapshot.body;\n if (bodyText && !bodyText.includes('{{')) return bodyText.substring(0, 80);\n }\n }\n if (typeof ad.title === 'string' && !ad.title.includes('{{')) return ad.title;\n if (typeof ad.ad_title === 'string' && ad.ad_title !== 'N/A') return ad.ad_title;\n return null;\n}\n\nfunction extractDate(ad) {\n if (ad.start_date_string) return ad.start_date_string.split('T')[0];\n if (typeof ad.start_date === 'number' && ad.start_date > 0) return new Date(ad.start_date * 1000).toISOString().split('T')[0];\n if (ad.start) return String(ad.start);\n if (ad.started_at) return String(ad.started_at);\n if (ad.created_at) return String(ad.created_at);\n return 'N/A';\n}\n\nfunction extractEndDate(ad) {\n if (ad.end_date_string) return ad.end_date_string.split('T')[0];\n if (typeof ad.end_date === 'number' && ad.end_date > 0) return new Date(ad.end_date * 1000).toISOString().split('T')[0];\n if (ad.last_seen) return String(ad.last_seen);\n if (ad.ended_at) return String(ad.ended_at);\n return 'N/A';\n}\n\nfunction buildSummary(rows) {\n if (rows.length === 0) return 'No ad data found.';\n const byPlatform = {};\n for (const r of rows) {\n byPlatform[r.platform] = (byPlatform[r.platform] || 0) + 1;\n }\n const platformSummary = Object.entries(byPlatform).map(([p, c]) => `${p}: ${c} ads`).join(', ');\n const dates = rows.map(r => r.campaign_start).filter(d => d && d !== 'N/A').sort();\n const earliest = dates[0] || 'unknown';\n const latest = dates[dates.length - 1] || 'unknown';\n const titles = [...new Set(rows.map(r => r.ad_title).filter(Boolean))].slice(0, 5).join('; ');\n return `Total ${rows.length} ads across platforms (${platformSummary}). Date range: ${earliest} to ${latest}. Sample titles: ${titles}`;\n}\n\n// ===== MAIN =====\n\nconst items = $input.all();\nconst timelineRows = [];\n\n// FIX: Pull domain & competitor_name from the Loop's current batch item\n// The splitInBatches node stores the current item in $('Loop Over Competitors').item\nlet domain = '';\nlet competitorName = '';\n\ntry {\n const loopItem = $('Loop Over Competitors').item.json;\n domain = loopItem.domain || loopItem.Domain || '';\n competitorName = loopItem.competitor_name || loopItem.Competitor_Name || loopItem.name || loopItem.Name || '';\n} catch(e) {\n // fallback: scan all items\n for (const item of items) {\n if (item.json.domain) { domain = item.json.domain; }\n if (item.json.competitor_name) { competitorName = item.json.competitor_name; }\n }\n}\n\nfor (const item of items) {\n const data = item.json;\n\n // Facebook/Meta\n if (data.results && Array.isArray(data.results)) {\n const fbAds = data.results.flat();\n for (const ad of fbAds) {\n const title = extractTitle(ad);\n if (!title) continue;\n timelineRows.push({\n platform: 'Meta',\n ad_title: title,\n campaign_start: extractDate(ad),\n campaign_end: extractEndDate(ad),\n is_active: ad.is_active || false\n });\n }\n }\n\n // LinkedIn / Google\n if (data.ads && Array.isArray(data.ads)) {\n for (const ad of data.ads) {\n let platform = 'Google';\n if (ad.view_details_link && ad.view_details_link.includes('linkedin.com')) {\n platform = 'LinkedIn';\n } else if (ad.headline || ad.commentary) {\n platform = 'LinkedIn';\n }\n\n const title = extractTitle(ad);\n if (!title) continue;\n\n timelineRows.push({\n platform: platform,\n ad_title: title,\n campaign_start: extractDate(ad),\n campaign_end: extractEndDate(ad),\n is_active: ad.is_active || false\n });\n }\n }\n}\n\nreturn [{\n json: {\n domain: domain,\n competitor_name: competitorName,\n timeline_rows: timelineRows,\n timeline_summary: buildSummary(timelineRows)\n }\n}];"
},
"typeVersion": 2
},
{
"id": "fc82659b-0bdc-4bc4-a397-739719e7b468",
"name": "Parse & Build Rows",
"type": "n8n-nodes-base.code",
"position": [
1760,
272
],
"parameters": {
"jsCode": "const items = $input.all();\nconst rows = [];\n\nlet timelineRows = [];\nlet domain = '';\nlet competitorName = '';\n\ntry {\n const extractData = $('Extract Timeline').first().json;\n timelineRows = extractData.timeline_rows || [];\n domain = extractData.domain || '';\n competitorName = extractData.competitor_name || '';\n} catch(e) {}\n\nfor (const ad of timelineRows) {\n rows.push({\n domain,\n competitor_name: competitorName,\n platform: ad.platform || 'N/A',\n ad_title: ad.ad_title || 'N/A',\n campaign_start: ad.campaign_start || 'N/A',\n campaign_end: ad.campaign_end || 'N/A',\n is_active: ad.is_active ? 'Yes' : 'No',\n checked_at: new Date().toISOString()\n });\n}\n\nreturn rows.map(r => ({ json: r }));"
},
"typeVersion": 2
},
{
"id": "26f6327d-3d75-471f-a387-961c640bd220",
"name": "Append to Launch Calendar",
"type": "n8n-nodes-base.googleSheets",
"position": [
1984,
272
],
"parameters": {
"columns": {
"value": {
"domain": "={{$json[\"domain\"]}}",
"ad_title": "={{$json[\"ad_title\"]}}",
"platform": "={{$json[\"platform\"]}}",
"is_active": "={{ $json.is_active }}",
"checked_at": "={{ $json.checked_at }}",
"campaign_end": "={{$json[\"campaign_end\"]}}",
"campaign_start": "={{$json[\"campaign_start\"]}}",
"competitor_name": "={{$json[\"competitor_name\"]}}"
},
"schema": [
{
"id": "domain",
"type": "string",
"display": true,
"required": false,
"displayName": "domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "competitor_name",
"type": "string",
"display": true,
"required": false,
"displayName": "competitor_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "platform",
"type": "string",
"display": true,
"required": false,
"displayName": "platform",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ad_title",
"type": "string",
"display": true,
"required": false,
"displayName": "ad_title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "campaign_start",
"type": "string",
"display": true,
"required": false,
"displayName": "campaign_start",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "campaign_end",
"type": "string",
"display": true,
"required": false,
"displayName": "campaign_end",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "is_active",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "is_active",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "checked_at",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "checked_at",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1092269994,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AA9I5PPuJEMDyWOE_BroajTXaHGJdcjC6ucL5ZiEtNM/edit#gid=1092269994",
"cachedResultName": "launch_calendar"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1AA9I5PPuJEMDyWOE_BroajTXaHGJdcjC6ucL5ZiEtNM",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1AA9I5PPuJEMDyWOE_BroajTXaHGJdcjC6ucL5ZiEtNM/edit?usp=drivesdk",
"cachedResultName": "Competitor Launch Calendar "
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "65452464-d121-45a2-9f11-76383e062b8d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-416,
-144
],
"parameters": {
"width": 704,
"height": 656,
"content": "# \ud83d\ude80 Competitor Launch Calendar\n\n\ud83d\udd0d How it works\n\nTracks competitor advertising activity to identify launch patterns and predict future campaign launches. The workflow collects ads across multiple platforms, builds a historical timeline, uses AI to analyze launch behavior, and stores predictions in a launch calendar.\n\n## \u2699\ufe0f How to set up\n- [ ] Connect Google Sheets credentials\n - [ ] Configure Adyntel API credentials\n- [ ] Add OpenAI credentials\n- [ ] Populate competitor names and domains\n - [ ] Set competitor status to Pending\n - [ ] Enable the weekly schedule trigger\n- [ ] Test with a small competitor list\n\n## \ud83d\udee0\ufe0f Customization\n- Change execution frequency\n- Add additional ad platforms\n- Modify launch prediction prompts\n- Add Slack or email notifications\n- Extend calendar storage to Airtable or databases"
},
"typeVersion": 1
},
{
"id": "b4c63f3f-f068-404a-b0e5-0948f9e5c735",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
336,
48
],
"parameters": {
"color": 6,
"width": 592,
"height": 352,
"content": "## \ud83d\udcc5 Workflow Trigger & Competitor Processing\n\nPurpose\nStarts the workflow on schedule, loads pending competitors from Google Sheets, and processes each competitor individually."
},
"typeVersion": 1
},
{
"id": "b3ee551a-f6a0-4fc2-bdc1-f128fcf9a577",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
992,
-48
],
"parameters": {
"color": 2,
"width": 432,
"height": 656,
"content": "## \ud83d\udce2 Multi-Platform Ad Collection\n\nPurpose\nCollects active advertising data from multiple channels and combines all campaign activity into a unified dataset."
},
"typeVersion": 1
},
{
"id": "dcc7ed88-bb41-4027-8f92-bd4f270c3d12",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1456,
16
],
"parameters": {
"color": 5,
"width": 224,
"height": 432,
"content": "## \ud83d\udcca Extract Timeline\nPurpose\nPulls ad titles, campaign start dates, end dates, and active status from the merged platform data. Structures everything into clean rows per ad, ready to be written directly to the tracker sheet."
},
"typeVersion": 1
},
{
"id": "1f75ac19-d607-47f0-b2d4-0c6bbbbfe5c0",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1728,
48
],
"parameters": {
"color": 4,
"width": 624,
"height": 560,
"content": "## \ud83d\uddc2\ufe0f Launch Calendar & Workflow Completion\n\nStores launch predictions, updates processing status, records calendar intelligence, and continues workflow execution for remaining competitors."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "3307def5-ea9a-430d-b00d-aa352d77650a",
"connections": {
"Extract Timeline": {
"main": [
[
{
"node": "Parse & Build Rows",
"type": "main",
"index": 0
}
]
]
},
"Read Competitors": {
"main": [
[
{
"node": "Loop Over Competitors",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Read Competitors",
"type": "main",
"index": 0
}
]
]
},
"Search Google Ads": {
"main": [
[
{
"node": "Merge All Platform Results",
"type": "main",
"index": 1
}
]
]
},
"Parse & Build Rows": {
"main": [
[
{
"node": "Append to Launch Calendar",
"type": "main",
"index": 0
}
]
]
},
"Search Facebook Ads": {
"main": [
[
{
"node": "Merge All Platform Results",
"type": "main",
"index": 0
}
]
]
},
"Search LinkedIn Ads": {
"main": [
[
{
"node": "Merge All Platform Results",
"type": "main",
"index": 2
}
]
]
},
"Loop Over Competitors": {
"main": [
[],
[
{
"node": "Search Facebook Ads",
"type": "main",
"index": 0
},
{
"node": "Search Google Ads",
"type": "main",
"index": 0
},
{
"node": "Search LinkedIn Ads",
"type": "main",
"index": 0
}
]
]
},
"Append to Launch Calendar": {
"main": [
[
{
"node": "Loop Over Competitors",
"type": "main",
"index": 0
}
]
]
},
"Merge All Platform Results": {
"main": [
[
{
"node": "Extract Timeline",
"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.
adyntelApigoogleSheetsOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This scheduled workflow reads pending competitors from Google Sheets, pulls their recent ads from Meta, Google, and LinkedIn via Adyntel, uses OpenAI to predict launch timing and seasonality, and appends the resulting timeline and predictions to a launch calendar sheet. Runs on…
Source: https://n8n.io/workflows/16306/ — 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.
AI-Powered Short-Form Video Generator with OpenAI, Flux, Kling, and ElevenLabs and upload to all .... Uses httpRequest, googleDrive, discord, googleSheets. Scheduled trigger; 51 nodes.
Free Support: Setting up and getting the workflow tailord to your needs. One small free adjustment included.
This workflow aims to help you and your team track your expenses with OpenAI It automatically collects your OpenAI organization’s API usage and cost data every few days and saves it to a ready-to-use
Automatically fetch, rank, and summarize the top financial stories from curated RSS feeds each day, then deliver a concise AI-written digest to Telegram and log the run to Google Sheets. Runs on a dai
This workflow runs twice daily to fetch recent Instagram post comments via the Instagram Graph API, uses OpenAI to classify sentiment, saves new positive comments to Google Sheets, and emails an HTML