AutomationFlowsAI & RAG › Track Expenses via Chat with Claude Haiku and Google Sheets

Track Expenses via Chat with Claude Haiku and Google Sheets

ByNirav Gajera @niravgajera on n8n.io

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.

Chat trigger trigger★★★★☆ complexityAI-powered27 nodesChat TriggerGoogle SheetsChain LlmAnthropic Chat
AI & RAG Trigger: Chat trigger Nodes: 27 Complexity: ★★★★☆ AI nodes: yes Added:

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 →

Download .json
{
  "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
          }
        ]
      ]
    }
  }
}
Pro

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 →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

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

Chat Trigger, HTTP Request, Google Sheets +8
AI & RAG

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

Chat Trigger, Chain Llm, OpenRouter Chat +8
AI & RAG

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

Agent, Chat Trigger, Chain Llm +6
AI & RAG

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

Google Gemini Chat, Chain Llm, Google Sheets +4
AI & RAG

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

Chat Trigger, Chat, Google Gemini Chat +4