This workflow corresponds to n8n.io template #14135 — we link there as the canonical source.
This workflow follows the Chainllm → Chat Trigger 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "ff5c621a-0deb-4067-864d-04bd59b6c2e5",
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"position": [
6480,
4560
],
"parameters": {
"options": {}
},
"typeVersion": 1.1
},
{
"id": "9806596c-ad71-48dc-a7b4-e4ffbc992dbd",
"name": "Detect Intent",
"type": "n8n-nodes-base.code",
"position": [
6704,
4560
],
"parameters": {
"jsCode": "\nconst chatInput = ($input.first().json.chatInput || $input.first().json.input || '').trim();\nconsole.log('Input:', chatInput);\nconst isSummary = /\\b(summary|report|total|spending|how much|breakdown|stats)\\b/i.test(chatInput);\nconst isHelp = /^(\\?|help|\\/help)$/i.test(chatInput);\nreturn [{ json: {\n chat_input: chatInput,\n intent: isSummary ? 'summary' : isHelp ? 'help' : 'expense'\n}}];\n"
},
"typeVersion": 2
},
{
"id": "9387f59f-2ce3-4d22-b8a8-7e48032f3ca2",
"name": "Intent Switch",
"type": "n8n-nodes-base.switch",
"position": [
6928,
4544
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "i1",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.intent }}",
"rightValue": "expense"
}
]
}
},
{
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "i2",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.intent }}",
"rightValue": "summary"
}
]
}
},
{
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "i3",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.intent }}",
"rightValue": "help"
}
]
}
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "57ea0ffb-1a76-4f6c-8e60-0e7619c7f4a7",
"name": "Read All Expenses",
"type": "n8n-nodes-base.googleSheets",
"position": [
7152,
4368
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1n7izUA8ACWfN-XqwOQkEkZ6lspUXsY1PMlmko67tLSg/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1n7izUA8ACWfN-XqwOQkEkZ6lspUXsY1PMlmko67tLSg",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1n7izUA8ACWfN-XqwOQkEkZ6lspUXsY1PMlmko67tLSg/edit?usp=drivesdk",
"cachedResultName": "Expense track"
}
},
"typeVersion": 4.5,
"alwaysOutputData": true
},
{
"id": "b9f18554-7c19-4969-86de-0a81be8c53d2",
"name": "Prepare Data",
"type": "n8n-nodes-base.code",
"position": [
7376,
4368
],
"parameters": {
"jsCode": "\n// This node ALWAYS outputs data regardless of how many rows the sheet has\n// Fixes: \"No item to return\" when sheet is empty\n\nconst allRows = $input.all();\nconst chatInput = $('Detect Intent').first().json.chat_input;\n\n// Calculate current month total from existing rows (0 if empty)\nconst now = new Date();\nconst currentMonth = now.toLocaleString('en-US', {month:'long'}) + ' ' + now.getFullYear();\n\nconst monthTotal = allRows\n .filter(r => (r.json['Month'] || '') === currentMonth)\n .reduce((sum, r) => sum + parseFloat(r.json['Amount'] || 0), 0);\n\nconsole.log('Rows in sheet:', allRows.length, '| Month total so far:', monthTotal);\n\n// Always return exactly 1 item so flow never stops\nreturn [{ json: {\n chat_input: chatInput,\n month_total: monthTotal,\n current_month: currentMonth,\n row_count: allRows.length\n}}];\n"
},
"typeVersion": 2
},
{
"id": "a433bab2-5943-49d2-a770-b75ecd7baa62",
"name": "AI Parse Expense",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
7600,
4368
],
"parameters": {
"text": "=You are an expense parser. Extract expense details from this message and return ONLY a JSON object, nothing else.\n\nMessage: \"{{ $('Detect Intent').first().json.chat_input }}\"\nToday: {{ new Date().toISOString().split('T')[0] }}\n\nReturn ONLY this JSON:\n{\"amount\": <number>, \"description\": \"<2-4 words>\", \"category\": \"<Food & Dining|Transport|Shopping|Bills & Utilities|Entertainment|Health|Business|Education|Other>\", \"currency\": \"<INR|USD|EUR|GBP>\", \"date\": \"<YYYY-MM-DD>\", \"is_expense\": <true|false>}\n\nRules:\n- If the message is clearly an expense with an amount \u2192 is_expense: true\n- If no amount can be found \u2192 is_expense: false\n- currency default: INR\n- date default: today\n\nExamples:\n\"spent 500 on car wash\" \u2192 {\"amount\":500,\"description\":\"Car wash\",\"category\":\"Transport\",\"currency\":\"INR\",\"date\":\"{{ new Date().toISOString().split('T')[0] }}\",\"is_expense\":true}\n\"test 500\" \u2192 {\"amount\":500,\"description\":\"Test\",\"category\":\"Other\",\"currency\":\"INR\",\"date\":\"{{ new Date().toISOString().split('T')[0] }}\",\"is_expense\":true}\n\"lunch 450\" \u2192 {\"amount\":450,\"description\":\"Lunch\",\"category\":\"Food & Dining\",\"currency\":\"INR\",\"date\":\"{{ new Date().toISOString().split('T')[0] }}\",\"is_expense\":true}\n\"hello\" \u2192 {\"amount\":0,\"description\":\"\",\"category\":\"Other\",\"currency\":\"INR\",\"date\":\"{{ new Date().toISOString().split('T')[0] }}\",\"is_expense\":false}\n\nJSON only. No explanation.",
"promptType": "define"
},
"typeVersion": 1.4
},
{
"id": "627fd6e3-2232-4f76-bafe-a4b01fbc0e51",
"name": "Claude Haiku",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"position": [
7744,
4544
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "claude-haiku-4-5-20251001",
"cachedResultName": "Claude Haiku 4.5"
},
"options": {}
},
"typeVersion": 1.3
},
{
"id": "218d3c40-8eb1-4dd8-b1b9-8dc01fa525b0",
"name": "Parse & Total",
"type": "n8n-nodes-base.code",
"position": [
7952,
4368
],
"parameters": {
"jsCode": "const rawText = ($json.text || $json.output || '').trim();\nconst prepared = $('Prepare Data').first().json;\nconst chatInput = prepared.chat_input;\nconsole.log('AI response:', rawText);\n\nlet parsed = null;\ntry { parsed = JSON.parse(rawText); } catch(e) {}\nif (!parsed) {\n try { const m = rawText.match(/\\{[\\s\\S]*\\}/); if(m) parsed = JSON.parse(m[0]); } catch(e) {}\n}\n\nif (!parsed || !parsed.is_expense || !parsed.amount || parseFloat(parsed.amount) <= 0) {\n return [{ json: {\n valid: false,\n reply: `I couldn't identify an expense amount in that message.\\n\\nPlease include an amount, like:\\n\u2022 \"spent 500 on lunch\"\\n\u2022 \"uber 150\"\\n\u2022 \"1200 electricity bill\"`\n }}];\n}\n\nconst emo = {'Food & Dining':'\ud83c\udf55','Transport':'\ud83d\ude97','Shopping':'\ud83d\udecd\ufe0f','Bills & Utilities':'\ud83d\udca1','Entertainment':'\ud83c\udfac','Health':'\ud83c\udfe5','Business':'\ud83d\udcbc','Education':'\ud83d\udcda','Other':'\ud83d\udcb0'};\nconst cat = parsed.category || 'Other';\nconst amount = parseFloat(parsed.amount);\nconst currency = parsed.currency || 'INR';\nconst symbol = currency === 'INR' ? '\u20b9' : currency + ' ';\nconst dateStr = parsed.date || new Date().toISOString().split('T')[0];\n\n// Derive month from parsed date (not today)\nconst parsedDate = new Date(dateStr);\nconst entryMonth = parsedDate.toLocaleString('en-US', {month:'long'}) + ' ' + parsedDate.getFullYear();\n\n// Running total for that specific month\nconst allRows = $('Read All Expenses').all();\nconst entryMonthTotal = allRows\n .filter(r => (r.json['Month'] || '') === entryMonth)\n .reduce((sum, r) => sum + parseFloat(r.json['Amount'] || 0), 0);\nconst newTotal = entryMonthTotal + amount;\n\nconsole.log(`Saving: ${symbol}${amount} | ${cat} | ${parsed.description} | Month: ${entryMonth} | Total: ${newTotal}`);\n\nreturn [{ json: {\n valid: true,\n Date: dateStr,\n Amount: amount,\n Category: cat,\n Description: parsed.description || chatInput,\n Currency: currency,\n Month: entryMonth,\n \"Raw Message\": chatInput,\n Total: newTotal,\n _display: `${emo[cat]||'\ud83d\udcb0'} *${cat}* \u2014 ${parsed.description || chatInput}\\n\ud83d\udcb3 ${symbol}${amount}\\n\ud83d\udcc5 ${dateStr}\\n\ud83d\udcca ${entryMonth} total: ${symbol}${newTotal.toFixed(0)}`\n}}];"
},
"typeVersion": 2
},
{
"id": "2a720602-ff73-44f5-9669-986e57be1d4d",
"name": "Is Valid Expense?",
"type": "n8n-nodes-base.if",
"position": [
8176,
4368
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "v1",
"operator": {
"type": "boolean",
"operation": "equals",
"rightType": "boolean"
},
"leftValue": "={{ $json.valid }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.1
},
{
"id": "52bdb900-a6c1-4676-83fb-c34f63ea9434",
"name": "Save Expense to Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
8400,
4272
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $json.Date }}",
"Month": "={{ $json.Month }}",
"Total": "={{ $json.Total }}",
"Amount": "={{ $json.Amount }}",
"Category": "={{ $json.Category }}",
"Currency": "={{ $json.Currency }}",
"Description": "={{ $json.Description }}",
"Raw Message": "={{ $json['Raw Message'] }}"
},
"schema": [
{
"id": "Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Amount",
"type": "string",
"display": true,
"required": false,
"displayName": "Amount",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Category",
"type": "string",
"display": true,
"required": false,
"displayName": "Category",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Currency",
"type": "string",
"display": true,
"required": false,
"displayName": "Currency",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Month",
"type": "string",
"display": true,
"required": false,
"displayName": "Month",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Raw Message",
"type": "string",
"display": true,
"required": false,
"displayName": "Raw Message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Total",
"type": "string",
"display": true,
"required": false,
"displayName": "Total",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1n7izUA8ACWfN-XqwOQkEkZ6lspUXsY1PMlmko67tLSg/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1n7izUA8ACWfN-XqwOQkEkZ6lspUXsY1PMlmko67tLSg",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1n7izUA8ACWfN-XqwOQkEkZ6lspUXsY1PMlmko67tLSg/edit?usp=drivesdk",
"cachedResultName": "Expense track"
}
},
"typeVersion": 4.5
},
{
"id": "de6afe46-3c00-459c-85e4-8cb613e5714b",
"name": "Reply Saved",
"type": "n8n-nodes-base.code",
"position": [
8624,
4272
],
"parameters": {
"jsCode": "\nconst d = $('Parse & Total').first().json;\nreturn [{ json: { output: `\u2705 Expense saved!\\n\\n${d._display}` }}];\n"
},
"typeVersion": 2
},
{
"id": "7d347763-8d31-48c4-aba7-f7ed6f332b6d",
"name": "Reply Invalid",
"type": "n8n-nodes-base.code",
"position": [
8400,
4464
],
"parameters": {
"jsCode": "\nconst d = $('Parse & Total').first().json;\nreturn [{ json: { output: d.reply || \"Please include an amount in your message.\" }}];\n"
},
"typeVersion": 2
},
{
"id": "e8261ff4-b303-47fd-a0f6-997f27f7b037",
"name": "Read for Summary",
"type": "n8n-nodes-base.googleSheets",
"position": [
7264,
4736
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1n7izUA8ACWfN-XqwOQkEkZ6lspUXsY1PMlmko67tLSg/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1n7izUA8ACWfN-XqwOQkEkZ6lspUXsY1PMlmko67tLSg",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1n7izUA8ACWfN-XqwOQkEkZ6lspUXsY1PMlmko67tLSg/edit?usp=drivesdk",
"cachedResultName": "Expense track"
}
},
"typeVersion": 4.5,
"alwaysOutputData": true
},
{
"id": "5da81e46-54f5-434c-bd13-21e6c8d1c00a",
"name": "Build Summary",
"type": "n8n-nodes-base.code",
"position": [
7472,
4736
],
"parameters": {
"jsCode": "const rows = $input.all();\nconst now = new Date();\nconst chatInput = $('Detect Intent').first().json.chat_input;\n\n// Detect if user asked for specific month\nconst monthNames = ['january','february','march','april','may','june','july','august','september','october','november','december'];\nlet targetMonth = null;\n\nconst lower = chatInput.toLowerCase();\nfor (const m of monthNames) {\n if (lower.includes(m)) {\n const yearMatch = lower.match(/20(\\d{2})/);\n const shortYearMatch = lower.match(/\\b(\\d{2})\\b/);\n let year = now.getFullYear();\n if (yearMatch) year = parseInt(yearMatch[0]);\n else if (shortYearMatch) year = 2000 + parseInt(shortYearMatch[1]);\n targetMonth = m.charAt(0).toUpperCase() + m.slice(1) + ' ' + year;\n break;\n }\n}\n\nif (!targetMonth) {\n targetMonth = now.toLocaleString('en-US', {month:'long'}) + ' ' + now.getFullYear();\n}\n\nconsole.log('Summary for month:', targetMonth);\n\nconst thisMonth = rows.filter(r => (r.json['Month'] || '') === targetMonth);\n\nif (rows.length === 0 || thisMonth.length === 0) {\n return [{ json: { output: `\ud83d\udcca No expenses found for *${targetMonth}*.\\n\\nTry: SUMMARY or \"summary march\"` }}];\n}\n\nconst total = thisMonth.reduce((s,r) => s + parseFloat(r.json['Amount']||0), 0);\nconst byCategory = {};\nfor (const r of thisMonth) {\n const cat = r.json['Category'] || 'Other';\n byCategory[cat] = (byCategory[cat]||0) + parseFloat(r.json['Amount']||0);\n}\nconst sorted = Object.entries(byCategory).sort((a,b) => b[1]-a[1]);\nconst emo = {'Food & Dining':'\ud83c\udf55','Transport':'\ud83d\ude97','Shopping':'\ud83d\udecd\ufe0f','Bills & Utilities':'\ud83d\udca1','Entertainment':'\ud83c\udfac','Health':'\ud83c\udfe5','Business':'\ud83d\udcbc','Education':'\ud83d\udcda','Other':'\ud83d\udcb0'};\nconst lines = sorted.map(([c,a]) => `${emo[c]||'\ud83d\udcb0'} ${c}: \u20b9${a.toFixed(0)} (${((a/total)*100).toFixed(0)}%)`).join('\\n');\n\nreturn [{ json: { output:\n `\ud83d\udcca *${targetMonth} Report*\\n\\n` +\n `\ud83d\udcb3 *Total: \u20b9${total.toFixed(0)}*\\n` +\n `\ud83d\udcdd Entries: ${thisMonth.length}\\n` +\n `\ud83d\udcc8 Daily avg: \u20b9${(total/now.getDate()).toFixed(0)}\\n` +\n `\ud83d\udd1d Top: ${emo[sorted[0]?.[0]]||'\ud83d\udcb0'} ${sorted[0]?.[0]}\\n\\n` +\n `*Breakdown:*\\n${lines}`\n}}];"
},
"typeVersion": 2
},
{
"id": "885991be-8d6f-49f5-b742-c57e3da0aaa3",
"name": "Send Help",
"type": "n8n-nodes-base.code",
"position": [
7152,
4976
],
"parameters": {
"jsCode": "\nreturn [{ json: { output:\n`\ud83d\udcb0 *Expense Tracker \u2014 Commands*\n\n\ud83d\udcdd *Add expense (just type naturally):*\n\u2022 \"spent 500 on lunch\"\n\u2022 \"paid 1200 electricity bill\"\n\u2022 \"uber 150\"\n\u2022 \"test 500\"\n\u2022 \"\u20b9450 groceries\"\n\u2022 \"netflix 499\"\n\n\ud83d\udcca *Monthly report:*\nType: SUMMARY\n\n\ud83d\udcb1 *Currencies:* \u20b9INR $USD \u20acEUR \u00a3GBP\n\n\ud83d\udcc2 *Auto-detected categories:*\n\ud83c\udf55 Food & Dining \ud83d\ude97 Transport\n\ud83d\udecd\ufe0f Shopping \ud83d\udca1 Bills & Utilities\n\ud83c\udfac Entertainment \ud83c\udfe5 Health\n\ud83d\udcbc Business \ud83d\udcda Education \ud83d\udcb0 Other`\n}}];\n"
},
"typeVersion": 2
},
{
"id": "fcb94238-8ccf-4b7f-9dff-c6813d5eecb7",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
6128,
3968
],
"parameters": {
"width": 476,
"height": 508,
"content": "## \ud83d\udcb0 AI Expense Tracker\nTrack expenses by chatting naturally.\n\n**Just type your expense:**\n_\"spent 500 on lunch\"_\n_\"uber 150\"_\n_\"1200 electricity bill\"_\n\n**Commands:**\n\u2022 `SUMMARY` \u2014 current month report\n\u2022 `summary february` \u2014 specific month\n\u2022 `HELP` \u2014 all commands\n\n**Currencies:** \u20b9 INR \u00b7 $ USD \u00b7 \u20ac EUR \u00b7 \u00a3 GBP"
},
"typeVersion": 1
},
{
"id": "fc55f4ca-d4d0-4f3d-aed3-4cf071cd0919",
"name": "Trigger Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
6192,
4512
],
"parameters": {
"color": 5,
"width": 424,
"height": 196,
"content": "## 1\ufe0f\u20e3 Entry Point\nReceives every chat message.\nPasses `chatInput` to next node."
},
"typeVersion": 1
},
{
"id": "fd4f6cce-59fc-4a26-8976-fb13377c9798",
"name": "Intent Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
6624,
4288
],
"parameters": {
"color": 5,
"height": 424,
"content": "## 2\ufe0f\u20e3 Detect Intent\nReads the message and classifies:\n\u2022 **expense** \u2014 anything with an amount\n\u2022 **summary** \u2014 report/total/breakdown\n\u2022 **help** \u2014 help/?"
},
"typeVersion": 1
},
{
"id": "452c8436-94d9-4e03-91a7-50e2406911db",
"name": "Switch Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
6880,
4288
],
"parameters": {
"color": 5,
"width": 220,
"height": 430,
"content": "## 3\ufe0f\u20e3 Routes to 3 paths\n**Output 0** \u2192 Expense flow\n**Output 1** \u2192 Summary flow\n**Output 2** \u2192 Help message"
},
"typeVersion": 1
},
{
"id": "29e8945f-d2ce-40c9-9730-8e63edd4a359",
"name": "Read Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
7104,
4080
],
"parameters": {
"color": 6,
"width": 204,
"height": 456,
"content": "## 4\ufe0f\u20e3 Read Sheet\nLoads all existing rows.\n`alwaysOutputData: true` ensures\nflow continues even if sheet is empty."
},
"typeVersion": 1
},
{
"id": "fbf91c41-741b-49c3-865b-471c296d0737",
"name": "Prepare Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
7312,
4080
],
"parameters": {
"color": 6,
"width": 220,
"height": 450,
"content": "## 5\ufe0f\u20e3 Prepare Data\nComputes running month total\nfrom existing rows.\nAlways outputs 1 item \u2192 flow\nnever stops on empty sheet."
},
"typeVersion": 1
},
{
"id": "fe8321dd-c9c0-4733-a70c-ad759db0bf8f",
"name": "AI Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
7552,
4080
],
"parameters": {
"color": 6,
"width": 300,
"height": 454,
"content": "## 6\ufe0f\u20e3 AI Parse\nClaude Haiku reads the message\nand extracts:\n\u2022 amount \u00b7 description\n\u2022 category \u00b7 currency \u00b7 date\n\u2022 is_expense (true/false)\n\nReturns clean JSON only."
},
"typeVersion": 1
},
{
"id": "740ca285-5709-4730-a206-0572bdb160ff",
"name": "Parse Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
7872,
4080
],
"parameters": {
"color": 6,
"width": 230,
"height": 450,
"content": "## 7\ufe0f\u20e3 Parse & Total\nValidates AI response.\nDerives Month from parsed date\n(not today \u2014 handles past entries).\nCalculates running monthly total."
},
"typeVersion": 1
},
{
"id": "00edd05c-dcd0-437f-9c4a-c5c1a994680e",
"name": "Valid Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
8112,
4080
],
"parameters": {
"color": 6,
"width": 212,
"height": 452,
"content": "## 8\ufe0f\u20e3 Valid?\n\u2705 TRUE \u2192 Save to sheet\n\u274c FALSE \u2192 Ask user\nto add an amount"
},
"typeVersion": 1
},
{
"id": "decba3df-1e53-4a14-9c87-bdc3a9303fca",
"name": "Save Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
8352,
4064
],
"parameters": {
"color": 6,
"width": 492,
"height": 536,
"content": "## 9\ufe0f\u20e3 Save + Reply\nAppends new row to sheet.\nReplies with confirmation:\n\u2705 Category \u00b7 Amount \u00b7 Date\n\ud83d\udcca Running month total"
},
"typeVersion": 1
},
{
"id": "9ad784c3-e3f3-41eb-85a7-72f6d3c475a0",
"name": "Summary Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
7104,
4560
],
"parameters": {
"color": 2,
"width": 596,
"height": 358,
"content": "## \ud83d\udcca SUMMARY FLOW\nReads all rows \u2192 filters by month.\nDetects specific month from message\n_(e.g. \"summary february 2026\")_\nDefaults to current month.\nShows total, breakdown, daily avg."
},
"typeVersion": 1
},
{
"id": "a1a82af4-5888-4681-8c82-1465c2eff8b0",
"name": "Sheet Columns",
"type": "n8n-nodes-base.stickyNote",
"position": [
6144,
4288
],
"parameters": {
"color": 7,
"width": 200,
"height": 180,
"content": "## \ud83d\udccb Sheet Columns\nA: Date\nB: Amount\nC: Category\nD: Description\nE: Currency\nF: Month\nG: Raw Message\nH: Total _(running monthly)_"
},
"typeVersion": 1
}
],
"connections": {
"Claude Haiku": {
"ai_languageModel": [
[
{
"node": "AI Parse Expense",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Prepare Data": {
"main": [
[
{
"node": "AI Parse Expense",
"type": "main",
"index": 0
}
]
]
},
"Detect Intent": {
"main": [
[
{
"node": "Intent Switch",
"type": "main",
"index": 0
}
]
]
},
"Intent Switch": {
"main": [
[
{
"node": "Read All Expenses",
"type": "main",
"index": 0
}
],
[
{
"node": "Read for Summary",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Help",
"type": "main",
"index": 0
}
]
]
},
"Parse & Total": {
"main": [
[
{
"node": "Is Valid Expense?",
"type": "main",
"index": 0
}
]
]
},
"AI Parse Expense": {
"main": [
[
{
"node": "Parse & Total",
"type": "main",
"index": 0
}
]
]
},
"Read for Summary": {
"main": [
[
{
"node": "Build Summary",
"type": "main",
"index": 0
}
]
]
},
"Is Valid Expense?": {
"main": [
[
{
"node": "Save Expense to Sheet",
"type": "main",
"index": 0
}
],
[
{
"node": "Reply Invalid",
"type": "main",
"index": 0
}
]
]
},
"Read All Expenses": {
"main": [
[
{
"node": "Prepare Data",
"type": "main",
"index": 0
}
]
]
},
"Save Expense to Sheet": {
"main": [
[
{
"node": "Reply Saved",
"type": "main",
"index": 0
}
]
]
},
"When chat message received": {
"main": [
[
{
"node": "Detect Intent",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow turns a simple chat interface into a powerful personal expense tracker. Just describe your spending in plain language — the AI understands it, categorizes it, and saves it to Google Sheets automatically.
Source: https://n8n.io/workflows/14135/ — 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.
This comprehensive workflow automates the complete financial document processing pipeline using AI. Upload invoices via chat, drop expense receipts into a folder, or add bank statements - the system a
The original LLM Council concept was introduced by Andrej Karpathy and published as an open-source repository demonstrating multi-model consensus and ranking. This workflow is my adaptation of that or
Paste any text and get a verdict on whether it was written by a human, AI, or a hybrid mix. Instead of trusting one black-box score, this workflow runs your text through statistical analysis and a thr
This workflow is designed for growth agencies, SaaS founders, and sales teams who want to move beyond static lead forms. It is ideal for those who need a "living" system that not only captures leads b
This workflow implements a multi-model AI orchestration with the BEST models at now (ChatGPT 5.2, Claude Opus 4.6, Gemini 3 Pro) and response aggregation system designed to handle user chat inputs int