AutomationFlowsAI & RAG › Scan Receipt Images From Google Drive with Gemini and Log to Google Sheets

Scan Receipt Images From Google Drive with Gemini and Log to Google Sheets

By柳田 優介 @yusuke on n8n.io

Freelancers, small business owners, and accountants who want to automate receipt management without manual data entry.

Event trigger★★★★☆ complexity17 nodesGoogle Drive TriggerGoogle DriveHTTP RequestGoogle Sheets
AI & RAG Trigger: Event Nodes: 17 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #15758 — we link there as the canonical source.

This workflow follows the Google Drive → Google Drive 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
{
  "id": "U1xq8YC1vSfgMG9D",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Scan receipt images in Google Drive with Gemini AI and log to Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "1dcbab05-b2b8-4de6-ba33-f5e2f982a065",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3792,
        2272
      ],
      "parameters": {
        "width": 380,
        "height": 736,
        "content": "### How it works\nDrop a receipt photo or PDF into your Google Drive folder \u2014 this workflow takes it from there. It grabs the new file, sends it to Gemini AI to read the receipt, and pulls out the important bits: date, store name, amount, tax, how you paid, and what kind of expense it is. All that gets added to your spreadsheet automatically, and the receipt moves to a \"processed\" folder so you don't scan it twice. If you dropped in a bunch of receipts at once, it works through them one by one.\n\n### Setup steps\n1. Make two Drive folders: one for new receipts, one for processed ones\n2. Create a spreadsheet with columns: date, store_name, total_amount, tax_amount, payment_method, category, source_file, original_file_id\n3. Get a Gemini API key from Google AI Studio and add it in n8n Settings \u2192 Variables as `GEMINI_API_KEY`\n4. Connect your Google Drive and Sheets OAuth2 credentials\n5. Set your folder IDs in Watch Drive Folder, Check Remaining Files, and Move to Processed\n6. Set the spreadsheet ID in Append to Sheet\n\n### Customization\n- Change what gets extracted by editing the prompt in Gemini OCR\n- Adjust polling frequency in Watch Drive Folder\n- Update expense categories to match your books"
      },
      "typeVersion": 1
    },
    {
      "id": "93c4fe09-29e0-4147-ad4f-5da77a96f8e1",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4208,
        2416
      ],
      "parameters": {
        "color": "#FCFCFC",
        "width": 438,
        "height": 452,
        "content": "## Trigger & filter\nPolls your Drive folder every minute. Only images and PDFs get through \u2014 everything else is ignored."
      },
      "typeVersion": 1
    },
    {
      "id": "a7ecb667-f921-4c1a-b688-06a7ef91a069",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4656,
        2416
      ],
      "parameters": {
        "color": "#FFFFFF",
        "width": 890,
        "height": 452,
        "content": "## OCR processing\nDownloads the receipt, converts it for Gemini, and parses the structured data that comes back."
      },
      "typeVersion": 1
    },
    {
      "id": "00482ee3-6622-4258-872c-34745a255569",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5552,
        2416
      ],
      "parameters": {
        "color": "#FFFFFF",
        "width": 424,
        "height": 452,
        "content": "## Save results\nAdds a new row to your spreadsheet and moves the receipt to the processed folder."
      },
      "typeVersion": 1
    },
    {
      "id": "d59fa54b-9357-4d33-b7ec-cb6969812942",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5984,
        2416
      ],
      "parameters": {
        "color": "#FFFFFF",
        "width": 920,
        "height": 452,
        "content": "## Batch loop\nChecks if there are more receipts waiting. If so, waits 30 seconds then grabs the next one."
      },
      "typeVersion": 1
    },
    {
      "id": "5c8f32d9-eb57-4ed9-871c-e979867d31b5",
      "name": "Watch Drive Folder",
      "type": "n8n-nodes-base.googleDriveTrigger",
      "position": [
        4272,
        2664
      ],
      "parameters": {
        "event": "fileCreated",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_UNPROCESSED_FOLDER_ID"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8fcf4fde-d1e5-4c21-8a33-6cfc0ed9295d",
      "name": "Filter Image or PDF",
      "type": "n8n-nodes-base.if",
      "position": [
        4496,
        2664
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition-if1",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.mimeType.startsWith('image/') || $json.mimeType === 'application/pdf' }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "afc9fd0f-72ad-4893-8a91-ab43ce14f982",
      "name": "Download File",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        4720,
        2664
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "options": {},
        "operation": "download"
      },
      "typeVersion": 3
    },
    {
      "id": "53496c23-de25-41c9-9322-8ef40bb6593b",
      "name": "Convert to Base64",
      "type": "n8n-nodes-base.code",
      "position": [
        4944,
        2592
      ],
      "parameters": {
        "jsCode": "const binaryKey = Object.keys(items[0].binary)[0];\nconst binaryData = items[0].binary[binaryKey];\n\nconst buffer = await this.helpers.getBinaryDataBuffer(0, binaryKey);\nconst base64 = buffer.toString('base64');\n\nreturn [{\n  json: {\n    base64Image: base64,\n    mimeType: binaryData.mimeType,\n    fileName: binaryData.fileName || 'unknown',\n    originalFileId: items[0].json?.id || $('Watch Drive Folder').item.json.id\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "4dd02c10-d8b3-4e6e-8121-552ca243eb76",
      "name": "Gemini OCR",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        5168,
        2592
      ],
      "parameters": {
        "url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={{ $env.GEMINI_API_KEY }}",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "jsonBody": "={\n  \"contents\": [{\n    \"parts\": [\n      {\n        \"inlineData\": {\n          \"mimeType\": \"{{ $json.mimeType }}\",\n          \"data\": \"{{ $json.base64Image }}\"\n        }\n      },\n      {\n        \"text\": \"Extract information from this receipt image and return ONLY in JSON format. Do not use markdown code blocks. Output JSON only.\\n\\nFollow these rules strictly:\\n- date: Return the date in YYYY-MM-DD format as a string\\n- store_name: Return the store name as a string\\n- total_amount: Return the total amount as an integer. Do not include currency symbols or commas. Example: 1980\\n- tax_amount: Return the tax amount as an integer. Return null if tax amount cannot be read\\n- payment_method: Return one of: Cash / Credit / E-money / QR Payment / Unknown\\n- category: Return the estimated expense category as a string (Supplies / Transportation / Entertainment / Communication / Miscellaneous)\\n\\nOutput example:\\n{\\\"date\\\": \\\"2026-04-01\\\", \\\"store_name\\\": \\\"Seven Eleven\\\", \\\"total_amount\\\": 1980, \\\"tax_amount\\\": 180, \\\"payment_method\\\": \\\"Cash\\\", \\\"category\\\": \\\"Supplies\\\"}\"\n      }\n    ]\n  }]\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "9b1b57b4-b52f-41e2-ba35-da80b9ad37ad",
      "name": "Parse JSON Response",
      "type": "n8n-nodes-base.code",
      "position": [
        5392,
        2592
      ],
      "parameters": {
        "jsCode": "// Extract JSON from Gemini response\nconst raw = $json.candidates[0].content.parts[0].text;\n// Handle ```json ... ``` fences if present\nlet cleaned = raw.replace(/```json\\s*/g, '').replace(/```\\s*/g, '').trim();\n// Extract all individual JSON objects\nconst objects = [];\nlet depth = 0;\nlet start = -1;\nfor (let i = 0; i < cleaned.length; i++) {\n  if (cleaned[i] === '{') {\n    if (depth === 0) start = i;\n    depth++;\n  } else if (cleaned[i] === '}') {\n    depth--;\n    if (depth === 0 && start !== -1) {\n      objects.push(cleaned.substring(start, i + 1));\n      start = -1;\n    }\n  }\n}\nif (objects.length === 0) {\n  throw new Error('Failed to extract JSON: ' + raw.substring(0, 200));\n}\nlet parsed;\nif (objects.length === 1) {\n  parsed = JSON.parse(objects[0]);\n} else {\n  parsed = JSON.parse('[' + objects.join(',') + ']');\n}\nif (!Array.isArray(parsed)) {\n  parsed = [parsed];\n}\nconst results = parsed.map(item => {\n  item.processed_at = new Date().toISOString();\n  item.source_file = items[0].json?.fileName || '';\n  item.original_file_id = items[0].json?.originalFileId || '';\n  return { json: item };\n});\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "75ff8339-cd4e-4024-8680-cbb3ed13e3ad",
      "name": "Append to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        5616,
        2592
      ],
      "parameters": {
        "columns": {
          "value": {
            "date": "={{ $json.date }}",
            "category": "={{ $json.category }}",
            "store_name": "={{ $json.store_name }}",
            "tax_amount": "={{ $json.tax_amount }}",
            "source_file": "={{ $json.source_file }}",
            "total_amount": "={{ $json.total_amount }}",
            "payment_method": "={{ $json.payment_method }}",
            "original_file_id": "={{ $json.original_file_id }}"
          },
          "schema": [
            {
              "id": "date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "store_name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "store_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "total_amount",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "total_amount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "tax_amount",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "tax_amount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "payment_method",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "payment_method",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "category",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "source_file",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "source_file",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "original_file_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "original_file_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_SPREADSHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "66186234-2948-4096-847f-74fef3b42b52",
      "name": "Move to Processed",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        5840,
        2592
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Convert to Base64').item.json.originalFileId }}"
        },
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "MyDrive"
        },
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_PROCESSED_FOLDER_ID"
        },
        "operation": "move"
      },
      "typeVersion": 3
    },
    {
      "id": "7b3fabc5-8ae3-49c3-ac06-4b4a91b9e623",
      "name": "Check Remaining Files",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        6064,
        2592
      ],
      "parameters": {
        "url": "https://www.googleapis.com/drive/v3/files",
        "options": {},
        "jsonQuery": "={\n  \"q\": \"'YOUR_UNPROCESSED_FOLDER_ID' in parents and trashed = false and (mimeType contains 'image/' or mimeType = 'application/pdf')\",\n  \"pageSize\": \"1\",\n  \"fields\": \"files(id, name, mimeType)\"\n}",
        "sendQuery": true,
        "specifyQuery": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleDriveOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "5f2343f0-d136-4f05-bc3a-524fd51ca525",
      "name": "Has More Files?",
      "type": "n8n-nodes-base.if",
      "position": [
        6288,
        2592
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition-loop",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.files.length }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "9bad1f31-43fa-4d46-b7e3-a22d075bf066",
      "name": "Wait 30s",
      "type": "n8n-nodes-base.wait",
      "position": [
        6512,
        2592
      ],
      "parameters": {
        "amount": 30
      },
      "typeVersion": 1.1
    },
    {
      "id": "50fb77a1-c2e4-4eca-b502-42e015fd88d2",
      "name": "Get Next File",
      "type": "n8n-nodes-base.code",
      "position": [
        6736,
        2664
      ],
      "parameters": {
        "jsCode": "const files = $('Check Remaining Files').item.json.files;\nreturn [{\n  json: {\n    id: files[0].id,\n    name: files[0].name\n  }\n}];"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "fc4af3b5-166c-411f-9131-0d05f92a2172",
  "connections": {
    "Wait 30s": {
      "main": [
        [
          {
            "node": "Get Next File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini OCR": {
      "main": [
        [
          {
            "node": "Parse JSON Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download File": {
      "main": [
        [
          {
            "node": "Convert to Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Next File": {
      "main": [
        [
          {
            "node": "Download File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append to Sheet": {
      "main": [
        [
          {
            "node": "Move to Processed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has More Files?": {
      "main": [
        [
          {
            "node": "Wait 30s",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to Base64": {
      "main": [
        [
          {
            "node": "Gemini OCR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Move to Processed": {
      "main": [
        [
          {
            "node": "Check Remaining Files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Watch Drive Folder": {
      "main": [
        [
          {
            "node": "Filter Image or PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Image or PDF": {
      "main": [
        [
          {
            "node": "Download File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse JSON Response": {
      "main": [
        [
          {
            "node": "Append to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Remaining Files": {
      "main": [
        [
          {
            "node": "Has More Files?",
            "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

Freelancers, small business owners, and accountants who want to automate receipt management without manual data entry.

Source: https://n8n.io/workflows/15758/ — 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

Monitor Google Drive folder, parsing PDF, DOCX and image file into a destination folder, ready for further processing (e.g. RAG ingestion, translation, etc.) Keep processing log in Google Sheet and se

Google Drive Trigger, Google Drive, HTTP Request +2
AI & RAG

Gemini File Search - Drive Watcher. Uses googleDriveTrigger, googleSheets, googleDrive, httpRequest. Event-driven trigger; 16 nodes.

Google Drive Trigger, Google Sheets, Google Drive +1
AI & RAG

The Problem That it Solves

Google Drive Trigger, OpenAI, Google Drive +5
AI & RAG

Content creators, YouTubers, and social media managers who want to repurpose long form videos into short clips without doing it manually. Works on self hosted n8n instances.

Google Drive Trigger, Google Drive, N8N Nodes Renderio +3
AI & RAG

Monitor a Google Drive folder, process each image based on the prompt defined in and save the new image to the specified output Google Drive folder. Maintain a processing log in Google Sheets.

Google Drive Trigger, Google Drive, HTTP Request +2