This workflow corresponds to n8n.io template #17733 — we link there as the canonical source.
This workflow follows the Google Sheets → HTTP Request 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 →
{
"id": "RgGP0o7EbesmgmbK",
"name": "Export Telegram Channel Posts to Google Sheets with Apify",
"tags": [],
"nodes": [
{
"id": "70000000-0000-0000-4000-8000-000000000001",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-1440,
176
],
"parameters": {},
"typeVersion": 1
},
{
"id": "70000000-0000-0000-4000-8000-000000000002",
"name": "Daily 09:00 Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-1440,
-16
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 9
}
]
}
},
"typeVersion": 1.3
},
{
"id": "70000000-0000-0000-4000-8000-000000000003",
"name": "1. Enter Telegram Channels",
"type": "n8n-nodes-base.set",
"position": [
-1056,
80
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "telegram-channels",
"name": "channels",
"type": "string",
"value": "durov\ntelegram"
},
{
"id": "telegram-per-channel",
"name": "Maximum Posts Per Channel",
"type": "number",
"value": 10
},
{
"id": "telegram-per-run",
"name": "Maximum Posts Per Run",
"type": "number",
"value": 20
}
]
}
},
"typeVersion": 3.4
},
{
"id": "70000000-0000-0000-4000-8000-000000000004",
"name": "Validate Channels and Build Actor Input",
"type": "n8n-nodes-base.code",
"position": [
-816,
80
],
"parameters": {
"jsCode": "const input = $input.first()?.json || {};\nconst channels = [...new Set(String(input.channels || '').split(/[\\n,]+/).map((value) => value.trim().replace(/^@/, '')).filter(Boolean))];\nconst maxPostsPerChannel = Math.max(1, Math.min(100, Math.floor(Number(input['Maximum Posts Per Channel']) || 10)));\nconst maximumPostsPerRun = Math.max(1, Math.min(500, Math.floor(Number(input['Maximum Posts Per Run']) || 20)));\nif (channels.length === 0) throw new Error('Enter at least one public Telegram channel handle.');\nif (channels.some((value) => !/^[A-Za-z0-9_]{3,64}$/.test(value))) throw new Error('Use public Telegram handles without t.me URLs.');\nreturn [{ json: {\n channels, maximumPostsPerRun, maximumPostsPerChannel: maxPostsPerChannel,\n intelligenceGoal: '', minimumRelevanceScore: 0, maxAlerts: 0,\n actorInput: { channels, maxPostsPerChannel, maxPosts: maximumPostsPerRun, includeComments: false, includeCommentCount: true, includeMedia: true, includeRawHtmlSnippet: false }\n} }];"
},
"typeVersion": 2
},
{
"id": "70000000-0000-0000-4000-8000-000000000005",
"name": "2. Run Telegram Channel Posts Scraper",
"type": "n8n-nodes-base.httpRequest",
"position": [
-576,
80
],
"parameters": {
"url": "https://api.apify.com/v2/acts/IXhSSJjFf5fz1XAir/runs",
"method": "POST",
"options": {
"timeout": 310000,
"response": {
"response": {
"responseFormat": "json"
}
}
},
"jsonBody": "={{ $json.actorInput }}",
"sendBody": true,
"sendQuery": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "waitForFinish",
"value": "300"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Accept-Encoding",
"value": "identity"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "70000000-0000-0000-4000-8000-000000000006",
"name": "Download Telegram Post Results",
"type": "n8n-nodes-base.httpRequest",
"position": [
-336,
80
],
"parameters": {
"url": "=https://api.apify.com/v2/datasets/{{ $json.data.defaultDatasetId }}/items",
"options": {
"timeout": 60000,
"response": {
"response": {
"responseFormat": "json"
}
}
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "clean",
"value": "true"
},
{
"name": "limit",
"value": "={{ $(\"Validate Channels and Build Actor Input\").first().json.maximumPostsPerRun }}"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "70000000-0000-0000-4000-8000-000000000007",
"name": "Prepare Human-Readable Post Rows",
"type": "n8n-nodes-base.code",
"position": [
-96,
80
],
"parameters": {
"jsCode": "\nconst config = $('Validate Channels and Build Actor Input').first().json;\nconst payloads = $input.all().flatMap((item) => {\n let value = item.json?.data ?? item.json;\n if (typeof value === 'string') {\n try { value = JSON.parse(value); } catch { throw new Error('Apify returned invalid JSON.'); }\n }\n return Array.isArray(value) ? value : [value];\n});\nconst sheetsEpochOffset = 25569;\nconst serial = (value) => {\n const date = new Date(value || Date.now());\n return Number.isFinite(date.getTime()) ? date.getTime() / 86400000 + sheetsEpochOffset : null;\n};\nconst numberOrNull = (value) => Number.isFinite(Number(value)) ? Number(value) : null;\nconst formula = (url, label) => url ? '=HYPERLINK(\"' + String(url).replace(/\"/g, '\"\"') + '\",\"' + String(label).replace(/\"/g, '\"\"') + '\")' : '';\nconst seen = new Set();\nconst rows = [];\nfor (const post of payloads) {\n const channelHandle = String(post.channelHandle || '').trim().replace(/^@/, '').toLowerCase();\n const postId = String(post.postId || '').trim();\n const postUrl = String(post.postUrl || (channelHandle && postId ? 'https://t.me/' + channelHandle + '/' + postId : ''));\n const text = String(post.text || '').trim();\n if (!channelHandle || !postId || !postUrl) continue;\n const itemKey = channelHandle + ':' + postId;\n if (seen.has(itemKey)) continue;\n seen.add(itemKey);\n rows.push({ json: {\n itemKey,\n collectedAt: serial(post.scrapedAt),\n channel: '@' + channelHandle,\n channelHandle,\n channelTitle: String(post.channelTitle || ''),\n subscribers: numberOrNull(post.subscriberCount),\n postDate: serial(post.date),\n postDateIso: post.date ? new Date(post.date).toISOString() : '',\n postLink: formula(postUrl, 'Open post'),\n postUrl,\n postId,\n text: text.slice(0, 12000),\n views: numberOrNull(post.views),\n reactions: numberOrNull(post.reactionCount),\n replies: numberOrNull(post.replyCount),\n commentsScraped: numberOrNull(post.commentsScraped),\n mediaCount: Array.isArray(post.mediaUrls) ? post.mediaUrls.length : 0,\n links: Array.isArray(post.links) ? post.links.join('\\n') : '',\n intelligenceGoal: config.intelligenceGoal,\n minimumRelevanceScore: config.minimumRelevanceScore,\n maxAlerts: config.maxAlerts\n } });\n if (rows.length >= config.maximumPostsPerRun) break;\n}\nif (rows.length === 0) throw new Error('No public Telegram posts were returned. Check the channel handles and date window.');\nreturn rows;"
},
"typeVersion": 2
},
{
"id": "70000000-0000-0000-4000-8000-000000000008",
"name": "3. Save Posts to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
144,
80
],
"parameters": {
"columns": {
"value": {
"Post": "={{ $json.postLink }}",
"Text": "={{ $json.text }}",
"Links": "={{ $json.links }}",
"Views": "={{ $json.views }}",
"Channel": "={{ $json.channel }}",
"Post ID": "={{ $json.postId }}",
"Replies": "={{ $json.replies }}",
"Post key": "={{ $json.itemKey }}",
"Post date": "={{ $json.postDate }}",
"Reactions": "={{ $json.reactions }}",
"Media count": "={{ $json.mediaCount }}",
"Subscribers": "={{ $json.subscribers }}",
"Collected at": "={{ $json.collectedAt }}",
"Channel title": "={{ $json.channelTitle }}",
"Comments scraped": "={{ $json.commentsScraped }}"
},
"schema": [
{
"id": "Post key",
"type": "string",
"display": true,
"required": false,
"displayName": "Post key",
"defaultMatch": true,
"canBeUsedToMatch": true
},
{
"id": "Collected at",
"type": "number",
"display": true,
"required": false,
"displayName": "Collected at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Channel",
"type": "string",
"display": true,
"required": false,
"displayName": "Channel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Channel title",
"type": "string",
"display": true,
"required": false,
"displayName": "Channel title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Subscribers",
"type": "number",
"display": true,
"required": false,
"displayName": "Subscribers",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Post date",
"type": "number",
"display": true,
"required": false,
"displayName": "Post date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Post",
"type": "string",
"display": true,
"required": false,
"displayName": "Post",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Post ID",
"type": "string",
"display": true,
"required": false,
"displayName": "Post ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Text",
"type": "string",
"display": true,
"required": false,
"displayName": "Text",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Views",
"type": "number",
"display": true,
"required": false,
"displayName": "Views",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Reactions",
"type": "number",
"display": true,
"required": false,
"displayName": "Reactions",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Replies",
"type": "number",
"display": true,
"required": false,
"displayName": "Replies",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Comments scraped",
"type": "number",
"display": true,
"required": false,
"displayName": "Comments scraped",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Media count",
"type": "number",
"display": true,
"required": false,
"displayName": "Media count",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Links",
"type": "string",
"display": true,
"required": false,
"displayName": "Links",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Post key"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {
"useAppend": true
},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "id",
"value": "7001001",
"cachedResultName": "Telegram Posts"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "1gZzH6q3pJ5fLwz37Cv0AjwTsyA-pdz2YtHa36QC6MIo",
"cachedResultName": "FetchCat n8n QA"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "70000000-0000-0000-4000-8000-000000000020",
"name": "Workflow Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2080,
-416
],
"parameters": {
"width": 480,
"height": 920,
"content": "## Export Telegram Channel Posts to Google Sheets\n\nCollect public Telegram channel posts with fetch_cat/telegram-channel-posts-scraper and keep one clean, sortable row per post in Google Sheets. This workflow works on n8n Cloud or self-hosted n8n, uses only built-in nodes, and does not require OpenAI.\n\n### How it works\n\n1. Starts manually or every day at 09:00.\n2. Reads one or more public channel handles and configurable post limits.\n3. Runs the Apify Actor once for the complete channel batch.\n4. Converts dates, links, engagement, media counts, and post text into human-readable columns.\n5. Updates Google Sheets by a stable channel-and-post key, so reruns refresh existing rows without creating duplicates.\n\n### Setup\n\n- [ ] Enter public channel handles without t.me URLs.\n- [ ] Choose maximum posts per channel and per workflow run.\n- [ ] Create an Apify HTTP Header Auth credential and select it in both HTTP Request nodes.\n- [ ] Copy the provided spreadsheet template, connect Google Sheets, and select its Telegram Posts tab.\n- [ ] Run manually, inspect the rows, and publish only when you are ready to enable the daily schedule.\n\nOnly public posts are collected. The workflow does not join channels, send messages, or access private Telegram data."
},
"typeVersion": 1
},
{
"id": "70000000-0000-0000-4000-8000-000000000021",
"name": "Start the Export",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1520,
-144
],
"parameters": {
"color": 7,
"width": 320,
"height": 480,
"content": "## Start the export\n\nRun manually during setup or publish the workflow to collect posts every day at 09:00."
},
"typeVersion": 1
},
{
"id": "70000000-0000-0000-4000-8000-000000000022",
"name": "Configure and Collect",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1120,
-144
],
"parameters": {
"color": 7,
"width": 720,
"height": 480,
"content": "## Configure and collect\n\nEnter public channel handles and post limits, validate them, then run the Apify Actor once for the complete batch."
},
"typeVersion": 1
},
{
"id": "70000000-0000-0000-4000-8000-000000000023",
"name": "Prepare and Save Posts",
"type": "n8n-nodes-base.stickyNote",
"position": [
-352,
-144
],
"parameters": {
"color": 7,
"width": 592,
"height": 480,
"content": "## Prepare and save posts\n\nCreate readable dates, direct links, metrics, and stable keys, then upsert one row per Telegram post in Google Sheets."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"timezone": "Europe/Lisbon",
"binaryMode": "separate",
"callerPolicy": "workflowsFromSameOwner",
"executionOrder": "v1",
"saveManualExecutions": true
},
"versionId": "5620803a-aae8-40c6-ab2c-75d938309733",
"nodeGroups": [],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "1. Enter Telegram Channels",
"type": "main",
"index": 0
}
]
]
},
"Daily 09:00 Trigger": {
"main": [
[
{
"node": "1. Enter Telegram Channels",
"type": "main",
"index": 0
}
]
]
},
"1. Enter Telegram Channels": {
"main": [
[
{
"node": "Validate Channels and Build Actor Input",
"type": "main",
"index": 0
}
]
]
},
"Download Telegram Post Results": {
"main": [
[
{
"node": "Prepare Human-Readable Post Rows",
"type": "main",
"index": 0
}
]
]
},
"Prepare Human-Readable Post Rows": {
"main": [
[
{
"node": "3. Save Posts to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"2. Run Telegram Channel Posts Scraper": {
"main": [
[
{
"node": "Download Telegram Post Results",
"type": "main",
"index": 0
}
]
]
},
"Validate Channels and Build Actor Input": {
"main": [
[
{
"node": "2. Run Telegram Channel Posts Scraper",
"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.
googleSheetsOAuth2ApihttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs manually or daily at 09:00 to scrape public Telegram channel posts using Apify and upsert them into Google Sheets as one clean row per post, including links, timestamps, and engagement metrics. Runs manually or on a daily schedule at 09:00. Reads the list of…
Source: https://n8n.io/workflows/17733/ — 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.
04 - Firecrawl URL List Mini-Batch to Resilient Analyzer. Uses googleSheets, httpRequest, executeWorkflowTrigger. Event-driven trigger; 42 nodes.
Automate LinkedIn lead generation by scraping comments from targeted posts and enriching profiles with detailed data
This workflow runs manually or every Monday to monitor a TikTok creator watchlist by scraping profile metrics with Apify, logging snapshots in Google Sheets, and sending baseline or change alerts to T
This automated n8n workflow scrapes job listings from Upwork using Apify, processes and cleans the data, and generates daily email reports with job summaries. The system uses Google Sheets for data st
Transform LinkedIn profile URLs into comprehensive enriched lead profiles, quickly and automatically.