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 →
{
"id": "[CENSORED]",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "(G) LineChatBot + Google Sheets (as a memory)",
"tags": [
{
"id": "[CENSORED]",
"name": "Guitar",
"createdAt": "2025-04-18T08:59:48.308Z",
"updatedAt": "2025-04-18T08:59:48.308Z"
}
],
"nodes": [
{
"id": "[CENSORED]",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
560,
-500
],
"parameters": {
"path": "guitarpa",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "[CENSORED]",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
460,
-220
],
"parameters": {
"text": "={{ $json.Prompt }}",
"options": {
"systemMessage": "=You are a helpful assistant. Your name is \"\u0e25\u0e25\u0e34\u0e15\u0e32\". You will help me in everything I need. You will answer based on user language. You are an AI Agent operating in the Thailand time zone (Asia/Bangkok, UTC+7). Today is {{ $now }}."
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.8
},
{
"id": "[CENSORED]",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
460,
-20
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.0-flash-001"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "[CENSORED]",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"position": [
780,
-500
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "[CENSORED]",
"name": "body.events[0].message.text",
"type": "string",
"value": "={{ $('Webhook').item.json.body.events[0].message.text }}"
},
{
"id": "[CENSORED]",
"name": "body.events[0].replyToken",
"type": "string",
"value": "={{ $('Webhook').item.json.body.events[0].replyToken }}"
},
{
"id": "[CENSORED]",
"name": "body.events[0].source.userId",
"type": "string",
"value": "={{ $json.body.events[0].source.userId }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "[CENSORED]",
"name": "HTTP Request",
"type": "n8n-nodes-base.httpRequest",
"position": [
1276,
-220
],
"parameters": {
"url": "https://api.line.me/v2/bot/message/reply",
"method": "POST",
"options": {},
"jsonBody": "={\n \"replyToken\": \"{{ $('Edit Fields').item.json.body.events[0].replyToken }}\",\n \"messages\": [\n {\n \"type\": \"text\",\n \"text\": \"{{ $('AI Agent').item.json.output.replaceAll('\\t', ' ').replaceAll('\\\"', '\\\\\\\"').replaceAll('\\n', '\\\\n').trim() || 'No response available.' }}\"\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer [CENSORED]"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "[CENSORED]",
"name": "Get History",
"type": "n8n-nodes-base.googleSheets",
"position": [
1000,
-500
],
"parameters": {
"options": {
"outputFormatting": {
"values": {
"date": "FORMATTED_STRING",
"general": "UNFORMATTED_VALUE"
}
},
"returnFirstMatch": true,
"dataLocationOnSheet": {
"values": {
"rangeDefinition": "detectAutomatically"
}
}
},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $('Webhook').item.json.body.events[0].source.userId }}",
"lookupColumn": "UserID "
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "[CENSORED]",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "[CENSORED]"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"notesInFlow": false,
"typeVersion": 4.5,
"alwaysOutputData": true
},
{
"id": "[CENSORED]",
"name": "Prepare Prompt",
"type": "n8n-nodes-base.set",
"position": [
1220,
-500
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "[CENSORED]",
"name": "Prompt",
"type": "string",
"value": "={{\n \"\u0e04\u0e38\u0e13\u0e04\u0e37\u0e2d\u0e25\u0e25\u0e34\u0e15\u0e32 \u0e41\u0e0a\u0e17\u0e1a\u0e2d\u0e17\u0e20\u0e32\u0e29\u0e32\u0e44\u0e17\u0e22\u0e17\u0e35\u0e48\u0e2a\u0e38\u0e20\u0e32\u0e1e\u0e41\u0e25\u0e30\u0e40\u0e1b\u0e47\u0e19\u0e21\u0e34\u0e15\u0e23 \u0e15\u0e2d\u0e1a\u0e15\u0e32\u0e21\u0e1a\u0e23\u0e34\u0e1a\u0e17\u0e02\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e2a\u0e19\u0e17\u0e19\u0e32:\\n\" +\n\n ($('Get History').item.json.History_Archive_1 || \"\") +\n (($('Get History').item.json.History_Archive_1) ? \"\\n\" : \"\") +\n\n ($('Get History').item.json.History_Archive_2 || \"\") +\n (($('Get History').item.json.History_Archive_2) ? \"\\n\" : \"\") +\n\n ($('Get History').item.json.History_Archive_3 || \"\") +\n (($('Get History').item.json.History_Archive_3) ? \"\\n\" : \"\") +\n\n ($('Get History').item.json.History_Archive_4 || \"\") +\n (($('Get History').item.json.History_Archive_4) ? \"\\n\" : \"\") +\n\n ($('Get History').item.json.History || \"\") +\n (($('Get History').item.json.History) ? \"\\n\" : \"\") +\n\n \"\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49: \" + $('Edit Fields').item.json.body.events[0].message.text + \"\\n\u0e25\u0e25\u0e34\u0e15\u0e32: \"\n}}\n"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "[CENSORED]",
"name": "Save History",
"type": "n8n-nodes-base.googleSheets",
"position": [
1056,
-220
],
"parameters": {
"columns": {
"value": {
"History": "={{ $('Split History').item.json.historyToSave }}",
"UserID ": "={{ $('Edit Fields').item.json.body.events[0].source.userId }}",
"LastUpdated": "={{ new Date().toISOString() }}",
"History_Archive_1": "={{ $('Split History').item.json.historyArchive1 }}",
"History_Archive_2": "={{ $('Split History').item.json.historyArchive2 }}",
"History_Archive_3": "={{ $('Split History').item.json.historyArchive3 }}",
"History_Archive_4": "={{ $('Split History').item.json.historyArchive4 }}"
},
"schema": [
{
"id": "UserID ",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "UserID ",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "History",
"type": "string",
"display": true,
"required": false,
"displayName": "History",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "LastUpdated",
"type": "string",
"display": true,
"required": false,
"displayName": "LastUpdated",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "History_Archive_1",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "History_Archive_1",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "History_Archive_2",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "History_Archive_2",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "History_Archive_3",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "History_Archive_3",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "History_Archive_4",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "History_Archive_4",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"UserID "
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "[CENSORED]",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "[CENSORED]"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "[CENSORED]",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
460,
-560
],
"parameters": {
"content": "### Connect to Line Official Account's API"
},
"typeVersion": 1
},
{
"id": "[CENSORED]",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
740,
-560
],
"parameters": {
"width": 180,
"content": "Prepare the data"
},
"typeVersion": 1
},
{
"id": "[CENSORED]",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
-560
],
"parameters": {
"width": 180,
"content": "Retrieve chat history"
},
"typeVersion": 1
},
{
"id": "[CENSORED]",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1180,
-560
],
"parameters": {
"width": 180,
"content": "Give our AI previous chat history"
},
"typeVersion": 1
},
{
"id": "[CENSORED]",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
460,
-280
],
"parameters": {
"content": "Get input with this command. \"{{ $json.Prompt }}\""
},
"typeVersion": 1
},
{
"id": "[CENSORED]",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
780,
-280
],
"parameters": {
"width": 180,
"content": "Split history into small chunks (data cleaning)"
},
"typeVersion": 1
},
{
"id": "[CENSORED]",
"name": "Split History",
"type": "n8n-nodes-base.code",
"position": [
840,
-220
],
"parameters": {
"jsCode": "// Get the current history, new message, and response\nlet history = $('Get History').item.json.History || '';\nlet message = $('Edit Fields').item.json.body.events[0].message.text;\nlet response = $json.output;\nlet newExchange = `\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49: ${message}\\n\u0e25\u0e25\u0e34\u0e15\u0e32: ${response}`;\nlet updatedHistory = history + (history ? '\\n' : '') + newExchange;\n\n// Threshold: 70% of Google Sheets cell limit (50,000 characters * 0.7 = 35,000)\nconst threshold = 35000;\nlet historyToSave = updatedHistory;\nlet archive1 = $('Get History').item.json.History_Archive_1 || '';\nlet archive2 = $('Get History').item.json.History_Archive_2 || '';\nlet archive3 = $('Get History').item.json.History_Archive_3 || '';\nlet archive4 = $('Get History').item.json.History_Archive_4 || '';\n\n// If history exceeds threshold, split it\nif (updatedHistory.length > threshold) {\n // Keep the last 17,500 characters in History (half of threshold for balance)\n const keepLength = 17500;\n const archiveChunk = updatedHistory.substring(0, updatedHistory.length - keepLength);\n historyToSave = updatedHistory.substring(updatedHistory.length - keepLength);\n\n // Distribute to archive cells, ensuring none exceed 35,000 characters\n if (archive1.length < threshold) {\n archive1 = (archive1 ? archive1 + '\\n' : '') + archiveChunk;\n if (archive1.length > threshold) {\n const excess = archive1.substring(threshold);\n archive1 = archive1.substring(0, threshold);\n if (archive2.length < threshold) {\n archive2 = (archive2 ? archive2 + '\\n' : '') + excess;\n }\n }\n }\n if (archive2.length < threshold && archive1.length >= threshold) {\n archive2 = (archive2 ? archive2 + '\\n' : '') + archiveChunk;\n if (archive2.length > threshold) {\n const excess = archive2.substring(threshold);\n archive2 = archive2.substring(0, threshold);\n if (archive3.length < threshold) {\n archive3 = (archive3 ? archive3 + '\\n' : '') + excess;\n }\n }\n }\n if (archive3.length < threshold && archive2.length >= threshold) {\n archive3 = (archive3 ? archive3 + '\\n' : '') + archiveChunk;\n if (archive3.length > threshold) {\n const excess = archive3.substring(threshold);\n archive3 = archive3.substring(0, threshold);\n if (archive4.length < threshold) {\n archive4 = (archive4 ? archive4 + '\\n' : '') + excess;\n }\n }\n }\n if (archive4.length < threshold && archive3.length >= threshold) {\n archive4 = (archive4 ? archive4 + '\\n' : '') + archiveChunk;\n if (archive4.length > threshold) {\n archive4 = archive4.substring(0, threshold);\n }\n }\n}\n\n// Return the values to update\nreturn [\n {\n json: {\n historyToSave: historyToSave,\n historyArchive1: archive1,\n historyArchive2: archive2,\n historyArchive3: archive3,\n historyArchive4: archive4,\n lastUpdated: new Date().toISOString()\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "[CENSORED]",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1000,
-280
],
"parameters": {
"width": 180,
"content": "Save to Google Sheets"
},
"typeVersion": 1
},
{
"id": "[CENSORED]",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
1220,
-280
],
"parameters": {
"width": 180,
"content": "Send it back to Line"
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "[CENSORED]",
"connections": {
"Webhook": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "Split History",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Get History",
"type": "main",
"index": 0
}
]
]
},
"Get History": {
"main": [
[
{
"node": "Prepare Prompt",
"type": "main",
"index": 0
}
]
]
},
"Save History": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"Split History": {
"main": [
[
{
"node": "Save History",
"type": "main",
"index": 0
}
]
]
},
"Prepare Prompt": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"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.
googlePalmApigoogleSheetsOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow enables a Line chatbot to maintain intelligent conversations by using Google Sheets as a persistent memory store, allowing it to recall past interactions and deliver personalised responses powered by Google's Gemini AI. It's ideal for developers or businesses building customer support bots on Line that need context-aware replies without complex backend databases. The key step involves the AI Agent querying and updating the Google Sheets history before generating each response via the Gemini Chat Model, ensuring seamless memory integration.
Use this when creating a simple, scalable chatbot for Line that requires conversation history but avoids heavy infrastructure, such as for FAQs or basic queries in e-commerce. Avoid it for high-volume traffic needing real-time databases like MongoDB, or if you require advanced multimodal AI beyond text. Common variations include swapping Gemini for another LLM or adding HTTP Request nodes to fetch external data for richer prompts.
About this workflow
(G) LineChatBot + Google Sheets (as a memory). Uses agent, lmChatGoogleGemini, httpRequest, googleSheets. Webhook trigger; 17 nodes.
Source: https://github.com/Zie619/n8n-workflows — 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.
⏺ 🚀 How it works
LineOA. Uses httpRequest, agent, lmChatGoogleGemini, outputParserStructured. Webhook trigger; 69 nodes.
Resume Screening & Behavioral Interviews with Gemini, Elevenlabs, & Notion ATS copy. Uses outputParserStructured, chainLlm, googleDrive, stickyNote. Webhook trigger; 67 nodes.
Candidate Engagement | Resume Screening | AI Voice Interviews | Applicant Insights
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.