AutomationFlowsSlack & Telegram › Extract Text From Google Drive Files to Google Sheets Using Nvidia Nim

Extract Text From Google Drive Files to Google Sheets Using Nvidia Nim

ByCordexa Technologies @cordexa on n8n.io

This template monitors Google Drive folder for new files, extracts text from PDFs, images, text files, CSVs, and Google Docs., reads images with meta/llama-3.2-11b-vision-instruct, structures the result with nvidia/llama-3.3-nemotron-super-49b-v1.5, logs everything to Google…

Event trigger★★★★★ complexity34 nodesGoogle Drive TriggerGoogle DriveGoogle DocsHTTP RequestTelegramGoogle Sheets
Slack & Telegram Trigger: Event Nodes: 34 Complexity: ★★★★★ Added:

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

This workflow follows the Google Docs → Google Drive 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
{
  "nodes": [
    {
      "id": "2a534e69-eb9d-4573-a57b-7e9222984945",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        -144
      ],
      "parameters": {
        "color": "#133FA4",
        "width": 420,
        "height": 732,
        "content": "# Extract Text From Google Drive Files\n\n**Who it's for:** Teams and individuals who need to automatically capture, structure, and log content from files dropped into a shared Google Drive folder.\n\n**What it does:** Monitors a Drive folder for new files, extracts text by file type, structures the result with NVIDIA NIM, logs it to Google Sheets, and sends a Telegram notification.\n\n**How it works:**\n1. Google Drive Trigger detects a new file\n2. File type is identified and routed (PDF, image, Google Docs, TXT, CSV)\n3. Text is extracted via the appropriate method for each type\n4. NVIDIA NIM structures the extracted text into a consistent schema\n5. Result is appended to a Google Sheets log\n6. Telegram notification confirms completion\n\n**Required setup:**\n- Google Drive OAuth2 credential\n- NVIDIA NIM API key (HTTP Header Auth)\n- Telegram bot credential\n- Google Sheets OAuth2 credential\n\nBuilt by Cordexa Technologies\nhttps://cordexa.tech\ncordexatech@gmail.com"
      },
      "typeVersion": 1
    },
    {
      "id": "3bea1e63-9a5d-46b7-a64c-871ccfcbe7b0",
      "name": "Section - Trigger and Input",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 606,
        "height": 740,
        "content": "## Step 1 \u2014 Config\n\nThis workflow watches a specific Google Drive folder every minute, normalizes file metadata, and routes each file by MIME type.\n\nBefore testing:\n- Replace `REPLACE_WITH_GOOGLE_DRIVE_FOLDER_ID` in `Google Drive Trigger` with your Drive folder ID.\n- Replace `REPLACE_WITH_TELEGRAM_CHAT_ID` in `Normalize Input` with your destination Telegram chat ID.\n\nSupported routes:\n- PDF\n- image\n- Google Doc\n- text file\n- CSV\n- unsupported fallback"
      },
      "typeVersion": 1
    },
    {
      "id": "cf8e5a70-a356-4928-a439-a07218e2fd80",
      "name": "Section - File Fetching",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 540,
        "height": 514,
        "content": "## File Fetching\nDownloads the file binary from Drive for local extraction (PDF, image, TXT, CSV) or fetches Google Doc content directly via the Docs API."
      },
      "typeVersion": 1
    },
    {
      "id": "c30bf8b9-954b-40b3-9afb-b3b06da0ccf4",
      "name": "Section - Extraction Branches",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 476,
        "height": 1056,
        "content": "## Extraction Branches\nExtracts raw text from each supported file type.\n- **PDF** \u2014 native Extract from File node\n- **TXT** \u2014 native Extract from File node\n- **CSV** \u2014 parsed to JSON rows\n- **Image** \u2014 NVIDIA Llama 3.2 vision model via HTTP\n- **Google Doc** \u2014 pre-fetched upstream, normalised here\n\nAll paths converge on a unified `content` field passed downstream."
      },
      "typeVersion": 1
    },
    {
      "id": "b401ae90-c106-4686-8a3c-e069984cd9e2",
      "name": "Section - Fallback Paths",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        640
      ],
      "parameters": {
        "color": 7,
        "width": 536,
        "height": 628,
        "content": "## Fallback Paths\nRaw text inputs bypass extraction entirely.\n\nUnsupported MIME types send a Telegram notification and stop \u2014 no Sheets row is written."
      },
      "typeVersion": 1
    },
    {
      "id": "9f515b82-5e33-4b1e-aade-e15632f5ac62",
      "name": "Section - AI Structuring",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1792,
        80
      ],
      "parameters": {
        "color": 7,
        "width": 884,
        "height": 558,
        "content": "## AI Structuring with NVIDIA NIM\nSends extracted text to `nvidia/llama-3.3-nemotron-super-49b-v1.5` with a guided JSON schema. Returns: title, summary, category, language, key points, and confidence notes. Falls back gracefully if the model call fails (`continueOnFail: true`).\n\n\n**Setup:** Add your NVIDIA NIM API key as an HTTP Header Auth credential (`Authorization: Bearer YOUR_KEY`). Apply it to both NVIDIA HTTP Request nodes."
      },
      "typeVersion": 1
    },
    {
      "id": "06c5660f-6546-4c81-9b74-424248fc3a62",
      "name": "Section - Log and Notify",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2720,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 510,
        "height": 736,
        "content": "## Step 3 \u2014 Delivery\n\nThis workflow delivers output in two places:\n\n- `Append Row in Sheet` writes a structured row to the `Extract_Log` tab\n- `Send Reply` sends a short Telegram confirmation message\n\nBefore going live:\n- Replace `REPLACE_WITH_GOOGLE_SHEET_ID` in `Append Row in Sheet`\n- Confirm the target sheet contains an `Extract_Log` tab\n- Confirm the sheet headers match the mapped fields in the node\n- Confirm Telegram replies go to the configured chat ID"
      },
      "typeVersion": 1
    },
    {
      "id": "f001b69a-0fee-413d-930a-b558021c9d02",
      "name": "Google Drive Trigger",
      "type": "n8n-nodes-base.googleDriveTrigger",
      "position": [
        144,
        304
      ],
      "parameters": {
        "event": "fileCreated",
        "options": {
          "fileType": "all"
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_GOOGLE_DRIVE_FOLDER_ID"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ce0aacba-4dcf-4a66-a5f6-8b2a719dadd1",
      "name": "Normalize Input",
      "type": "n8n-nodes-base.code",
      "position": [
        336,
        304
      ],
      "parameters": {
        "jsCode": "const fileId = $json.id || $json.fileId || '';\nconst rawMimeType = ($json.mimeType || '').toString();\nconst fileName = ($json.name || $json.fileName || 'drive-file').toString();\nconst mimeType = rawMimeType;\nconst googleDocId = mimeType === 'application/vnd.google-apps.document' ? fileId : '';\nconst googleDocUrl = googleDocId ? `https://docs.google.com/document/d/${googleDocId}` : '';\n\nlet route = 'unsupported';\nif (mimeType === 'application/pdf') {\n  route = 'pdf';\n} else if (mimeType.startsWith('image/')) {\n  route = 'image';\n} else if (mimeType === 'application/vnd.google-apps.document') {\n  route = 'google_doc';\n} else if (mimeType === 'text/csv' || fileName.toLowerCase().endsWith('.csv')) {\n  route = 'csv';\n} else if (mimeType.startsWith('text/')) {\n  route = 'text_file';\n}\n\nreturn [{\n  json: {\n    route,\n    fileId,\n    rawText: '',\n    chatId: 'REPLACE_WITH_TELEGRAM_CHAT_ID',\n    messageId: '',\n    mimeType,\n    fileName,\n    binaryKey: '',\n    googleDocUrl,\n    googleDocId,\n    sourceType: 'google_drive_file',\n    runId: $execution.id\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "9d174ac1-d0d4-4abf-9922-d3646a5107a4",
      "name": "Route Input",
      "type": "n8n-nodes-base.switch",
      "position": [
        576,
        304
      ],
      "parameters": {
        "rules": {
          "rules": [
            {
              "value2": "pdf",
              "outputKey": "pdf"
            },
            {
              "value2": "image",
              "outputKey": "image"
            },
            {
              "value2": "google_doc",
              "outputKey": "google_doc"
            },
            {
              "value2": "text_file",
              "outputKey": "text_file"
            },
            {
              "value2": "csv",
              "outputKey": "csv"
            },
            {
              "value2": "raw_text",
              "outputKey": "raw_text"
            }
          ]
        },
        "value1": "={{ $json.route }}",
        "dataType": "string"
      },
      "typeVersion": 2
    },
    {
      "id": "c6e2b331-1781-4f5f-9750-f3c3eba0fbfc",
      "name": "Download Drive File",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        832,
        240
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.fileId }}"
        },
        "options": {
          "fileName": "={{ $json.fileName }}"
        },
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "abddb5e5-6994-47f6-b766-65e388014cdf",
      "name": "Route Downloaded File",
      "type": "n8n-nodes-base.switch",
      "position": [
        1072,
        208
      ],
      "parameters": {
        "rules": {
          "rules": [
            {
              "value2": "pdf",
              "outputKey": "pdf"
            },
            {
              "value2": "image",
              "outputKey": "image"
            },
            {
              "value2": "text_file",
              "outputKey": "text_file"
            },
            {
              "value2": "csv",
              "outputKey": "csv"
            }
          ]
        },
        "value1": "={{ $('Normalize Input').item.json.route }}",
        "dataType": "string"
      },
      "typeVersion": 2
    },
    {
      "id": "9f0b9eaf-2ae4-4666-a35f-2f89b585a5cf",
      "name": "Get Google Doc",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        832,
        432
      ],
      "parameters": {
        "operation": "get",
        "documentURL": "={{ $('Normalize Input').first().json.googleDocId }}"
      },
      "typeVersion": 2
    },
    {
      "id": "96709023-7020-49c3-a2ce-ae6280e3b11b",
      "name": "Normalize Google Doc",
      "type": "n8n-nodes-base.code",
      "position": [
        1072,
        432
      ],
      "parameters": {
        "jsCode": "function pickLongestString(value) {\n  let best = '';\n  const visit = (v) => {\n    if (typeof v === 'string') {\n      if (v.length > best.length) best = v;\n      return;\n    }\n    if (Array.isArray(v)) {\n      for (const item of v) visit(item);\n      return;\n    }\n    if (v && typeof v === 'object') {\n      for (const key of Object.keys(v)) visit(v[key]);\n    }\n  };\n  visit(value);\n  return best;\n}const content = pickLongestString($input.first().json) || JSON.stringify($input.first().json);\nreturn [{\n  json: {\n    content,\n    fileType: 'google_doc',\n    fileName: $('Normalize Input').first().json.fileName || 'Google Doc',\n    chatId: $('Normalize Input').first().json.chatId,\n    messageId: $('Normalize Input').first().json.messageId,\n    sourceType: 'google_drive_google_doc',\n    runId: $('Normalize Input').first().json.runId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "db8828df-f8f1-4a92-977d-b2d0fa960f45",
      "name": "Extract from PDF",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        1328,
        112
      ],
      "parameters": {
        "options": {},
        "operation": "pdf"
      },
      "typeVersion": 1
    },
    {
      "id": "6753414a-fa67-4168-9bff-9b6379ecdd9e",
      "name": "Normalize PDF",
      "type": "n8n-nodes-base.code",
      "position": [
        1568,
        112
      ],
      "parameters": {
        "jsCode": "function pickLongestString(value) {\n  let best = '';\n  const visit = (v) => {\n    if (typeof v === 'string') {\n      if (v.length > best.length) best = v;\n      return;\n    }\n    if (Array.isArray(v)) {\n      for (const item of v) visit(item);\n      return;\n    }\n    if (v && typeof v === 'object') {\n      for (const key of Object.keys(v)) visit(v[key]);\n    }\n  };\n  visit(value);\n  return best;\n}const content = pickLongestString($input.first().json) || JSON.stringify($input.first().json);\nreturn [{\n  json: {\n    content,\n    fileType: 'pdf',\n    fileName: $('Normalize Input').first().json.fileName,\n    chatId: $('Normalize Input').first().json.chatId,\n    messageId: $('Normalize Input').first().json.messageId,\n    sourceType: 'google_drive_pdf',\n    runId: $('Normalize Input').first().json.runId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "53b6ef38-ad3c-4b50-ac40-e04e5503479a",
      "name": "Prepare Image Data URL",
      "type": "n8n-nodes-base.code",
      "position": [
        1328,
        272
      ],
      "parameters": {
        "jsCode": "const item = $input.first();\nconst binaryKey = $('Normalize Input').first().json.binaryKey || Object.keys(item.binary || {})[0] || 'data';\n\nif (!binaryKey || !item.binary || !item.binary[binaryKey]) {\n  throw new Error('No binary image found on the input item.');\n}\n\nconst bin = item.binary[binaryKey];\nconst mimeType = (bin.mimeType || 'image/jpeg').toLowerCase();\n\nconst buffer = await this.helpers.getBinaryDataBuffer(0, binaryKey);\nconst base64 = Buffer.from(buffer).toString('base64');\n\nif (!base64) {\n  throw new Error('Could not convert input image to base64.');\n}\n\nconst dataUrl = `data:${mimeType};base64,${base64}`;\n\nreturn [{\n  json: {\n    imageDataUrl: dataUrl,\n    fileType: 'image',\n    fileName: $('Normalize Input').first().json.fileName,\n    chatId: $('Normalize Input').first().json.chatId,\n    messageId: $('Normalize Input').first().json.messageId,\n    sourceType: 'google_drive_image',\n    runId: $('Normalize Input').first().json.runId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "79133794-159c-4987-87bb-80c0821922a5",
      "name": "Build Image Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        1568,
        272
      ],
      "parameters": {
        "jsCode": "const payload = {\n  model: 'meta/llama-3.2-11b-vision-instruct',\n  messages: [\n    {\n      role: 'system',\n      content: 'You are an OCR extraction assistant. Extract all readable text from the image exactly as written. Return plain text only. No markdown, no bullets, no commentary, no labels, no JSON.'\n    },\n    {\n      role: 'user',\n      content: [\n        {\n          type: 'text',\n          text: 'Extract all readable text from this image exactly as written. Preserve line breaks when possible. If no readable text is visible, return exactly: NO_TEXT_EXTRACTED'\n        },\n        {\n          type: 'image_url',\n          image_url: {\n            url: $json.imageDataUrl\n          }\n        }\n      ]\n    }\n  ],\n  temperature: 0,\n  top_p: 1,\n  max_tokens: 1800,\n  stream: false\n};\n\nreturn [{\n  json: {\n    nimPayload: JSON.stringify(payload),\n    fileType: $json.fileType,\n    fileName: $json.fileName,\n    chatId: $json.chatId,\n    messageId: $json.messageId,\n    sourceType: $json.sourceType,\n    runId: $json.runId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d5e49e77-6f5a-4421-875d-0e249be73d27",
      "name": "Analyze Image with NVIDIA",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1824,
        272
      ],
      "parameters": {
        "url": "https://integrate.api.nvidia.com/v1/chat/completions",
        "body": "={{ $json.nimPayload }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "5516a7f4-c51c-462d-9a4d-f51bf226e06e",
      "name": "Normalize Image",
      "type": "n8n-nodes-base.code",
      "position": [
        2096,
        272
      ],
      "parameters": {
        "jsCode": "const rawContent = $json.choices?.[0]?.message?.content;\n\nlet text = '';\n\nif (typeof rawContent === 'string') {\n  text = rawContent;\n} else if (Array.isArray(rawContent)) {\n  text = rawContent\n    .map(part => {\n      if (typeof part === 'string') return part;\n      if (part?.type === 'text' && typeof part.text === 'string') return part.text;\n      return '';\n    })\n    .filter(Boolean)\n    .join('\\n');\n}\n\ntext = (text || '')\n  .replace(/```[\\s\\S]*?```/g, '')\n  .trim();\n\nif (!text || text === 'NO_TEXT_EXTRACTED') {\n  text = 'No text extracted';\n}\n\nreturn [{\n  json: {\n    content: text,\n    fileType: $('Build Image Payload').first().json.fileType,\n    fileName: $('Build Image Payload').first().json.fileName,\n    chatId: $('Build Image Payload').first().json.chatId,\n    messageId: $('Build Image Payload').first().json.messageId,\n    sourceType: $('Build Image Payload').first().json.sourceType,\n    runId: $('Build Image Payload').first().json.runId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "75db9680-186f-4594-bda3-a5928b19101b",
      "name": "Extract from Text File",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        1328,
        592
      ],
      "parameters": {
        "options": {},
        "operation": "text"
      },
      "typeVersion": 1
    },
    {
      "id": "42b50c4a-2811-4722-a166-d3d9d807e1c6",
      "name": "Normalize Text File",
      "type": "n8n-nodes-base.code",
      "position": [
        1568,
        592
      ],
      "parameters": {
        "jsCode": "function pickLongestString(value) {\n  let best = '';\n  const visit = (v) => {\n    if (typeof v === 'string') {\n      if (v.length > best.length) best = v;\n      return;\n    }\n    if (Array.isArray(v)) {\n      for (const item of v) visit(item);\n      return;\n    }\n    if (v && typeof v === 'object') {\n      for (const key of Object.keys(v)) visit(v[key]);\n    }\n  };\n  visit(value);\n  return best;\n}const content = pickLongestString($input.first().json) || JSON.stringify($input.first().json);\nreturn [{\n  json: {\n    content,\n    fileType: 'text_file',\n    fileName: $('Normalize Input').first().json.fileName,\n    chatId: $('Normalize Input').first().json.chatId,\n    messageId: $('Normalize Input').first().json.messageId,\n    sourceType: 'google_drive_text_file',\n    runId: $('Normalize Input').first().json.runId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ca006ec2-196b-4611-811c-7c6389526cab",
      "name": "Extract from CSV",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        1328,
        752
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "ad4d4750-c19a-40ab-92d9-c68b48a6d60b",
      "name": "Normalize CSV",
      "type": "n8n-nodes-base.code",
      "position": [
        1568,
        752
      ],
      "parameters": {
        "jsCode": "const rows = $input.all().map(item => item.json);\nconst content = JSON.stringify(rows, null, 2);\nreturn [{\n  json: {\n    content,\n    fileType: 'csv',\n    fileName: $('Normalize Input').first().json.fileName,\n    chatId: $('Normalize Input').first().json.chatId,\n    messageId: $('Normalize Input').first().json.messageId,\n    sourceType: 'google_drive_csv',\n    runId: $('Normalize Input').first().json.runId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "f54a8128-9e91-4d2d-b4bf-aed3f7ed5613",
      "name": "Normalize Raw Text",
      "type": "n8n-nodes-base.code",
      "position": [
        832,
        912
      ],
      "parameters": {
        "jsCode": "return [{\n  json: {\n    content: $('Normalize Input').first().json.rawText,\n    fileType: 'raw_text',\n    fileName: 'telegram-message',\n    chatId: $('Normalize Input').first().json.chatId,\n    messageId: $('Normalize Input').first().json.messageId,\n    sourceType: 'google_drive_text',\n    runId: $('Normalize Input').first().json.runId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "105c960a-a06c-43bb-bb85-3944fac89225",
      "name": "Send Unsupported Message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        832,
        1088
      ],
      "parameters": {
        "text": "=Unsupported Google Drive file.\n\nSupported types: PDF, image, Google Docs, text file, and CSV.",
        "chatId": "={{ $('Normalize Input').first().json.chatId }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "c727e5de-c8ba-4d1c-9ee8-1be0794719ae",
      "name": "Build Structuring Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        1904,
        432
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst rawContent = (input.content || '').toString().trim();\nconst trimmedContent = rawContent.slice(0, 12000);\n\nconst schema = {\n  type: \"object\",\n  properties: {\n    title: { type: \"string\" },\n    summary: { type: \"string\" },\n    category: { type: \"string\" },\n    language: { type: \"string\" },\n    key_points: {\n      type: \"array\",\n      items: { type: \"string\" }\n    },\n    confidence_notes: { type: \"string\" }\n  },\n  required: [\"title\", \"summary\", \"category\", \"language\", \"key_points\", \"confidence_notes\"]\n};\n\nconst system = [\n  'You are a document extraction and structuring assistant.',\n  'Structure the already extracted text.',\n  'Return only the schema fields.',\n  'No reasoning.',\n  'No commentary.',\n  'No <think> tags.',\n  'No markdown fences.',\n  'Do not hallucinate missing facts.',\n  'If unknown, use empty string or empty array.'\n].join('\\n');\n\nconst user = [\n  `Source type: ${input.fileType}`,\n  `File name: ${input.fileName}`,\n  '',\n  'Text to structure:',\n  trimmedContent\n].join('\\n');\n\nconst payload = {\n  model: 'nvidia/llama-3.3-nemotron-super-49b-v1.5',\n  messages: [\n    { role: 'system', content: system },\n    { role: 'user', content: user }\n  ],\n  temperature: 0,\n  top_p: 1,\n  max_tokens: 600,\n  stream: false,\n  guided_json: schema\n};\n\nreturn [{\n  json: {\n    nimPayload: JSON.stringify(payload),\n    originalContent: rawContent,\n    fileType: input.fileType,\n    fileName: input.fileName,\n    chatId: input.chatId,\n    messageId: input.messageId,\n    sourceType: input.sourceType,\n    runId: input.runId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "9aa50389-31c1-467a-b494-9739001b3b12",
      "name": "Structure Output with NVIDIA",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2208,
        432
      ],
      "parameters": {
        "url": "https://integrate.api.nvidia.com/v1/chat/completions",
        "body": "={{ $json.nimPayload }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "9ae98693-24ea-4bee-8cfc-7f47a174f299",
      "name": "Parse Structured Output",
      "type": "n8n-nodes-base.code",
      "position": [
        2464,
        432
      ],
      "parameters": {
        "jsCode": "function safeParse(text) {\n  if (!text) return null;\n  try {\n    return JSON.parse(text);\n  } catch (e) {}\n  const match = text.match(/\\{[\\s\\S]*\\}/);\n  if (match) {\n    try {\n      return JSON.parse(match[0]);\n    } catch (e) {}\n  }\n  return null;\n}\n\nconst raw = $json.choices?.[0]?.message?.content?.trim() || '';\nconst cleanedRaw = raw.replace(/<think>[\\s\\S]*?<\\/think>/g, '').trim();\nconst parsed = safeParse(cleanedRaw) || {};\nconst original = $('Build Structuring Payload').first().json.originalContent || '';\n\nreturn [{\n  json: {\n    title: parsed.title || $('Build Structuring Payload').first().json.fileName || 'Untitled',\n    summary: parsed.summary || original.slice(0, 300),\n    category: parsed.category || '',\n    language: parsed.language || '',\n    key_points: Array.isArray(parsed.key_points) ? parsed.key_points : [],\n    confidence_notes: parsed.confidence_notes || '',\n    extracted_text: original,\n    sourceType: $('Build Structuring Payload').first().json.sourceType,\n    fileType: $('Build Structuring Payload').first().json.fileType,\n    fileName: $('Build Structuring Payload').first().json.fileName,\n    chatId: $('Build Structuring Payload').first().json.chatId,\n    messageId: $('Build Structuring Payload').first().json.messageId,\n    runId: $('Build Structuring Payload').first().json.runId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c9c31b49-b926-46a8-a258-513b99098928",
      "name": "Append Row in Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2768,
        336
      ],
      "parameters": {
        "columns": {
          "value": {
            "Title": "={{ $json.title }}",
            "Run ID": "={{ $json.runId }}",
            "Summary": "={{ $json.summary }}",
            "Category": "={{ $json.category }}",
            "Language": "={{ $json.language }}",
            "File Name": "={{ $json.fileName }}",
            "File Type": "={{ $json.fileType }}",
            "Timestamp": "={{ new Date().toISOString() }}",
            "Key Points": "={{ ($json.key_points || []).join(' | ') }}",
            "Source Type": "={{ $json.sourceType }}",
            "Extracted Text": "={{ $json.extracted_text }}",
            "Confidence Notes": "={{ $json.confidence_notes }}"
          },
          "schema": [
            {
              "id": "Timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Source Type",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Source Type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "File Type",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "File Type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "File Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "File Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Summary",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Summary",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Category",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Language",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Language",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Key Points",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Key Points",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Confidence Notes",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Confidence Notes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Extracted Text",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Extracted Text",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Run ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Run ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Extract_Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "REPLACE_WITH_GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5,
      "continueOnFail": true
    },
    {
      "id": "20cbfebd-8602-40f4-8b79-307a7f9d9dbd",
      "name": "Build Telegram Reply",
      "type": "n8n-nodes-base.code",
      "position": [
        2768,
        528
      ],
      "parameters": {
        "jsCode": "let text = [\n  'FILE PROCESSED',\n  '',\n  `Name: ${$json.fileName || ''}`,\n  `Type: ${$json.fileType || ''}`,\n  `Title: ${$json.title || ''}`,\n  `Category: ${$json.category || ''}`,\n  '',\n  'Status: Extracted and logged to Google Sheets.'\n].filter(Boolean).join('\\n');\n\nif (text.length > 3500) {\n  text = text.slice(0, 3490) + '\\n\u2026';\n}\n\nreturn [{\n  json: {\n    replyText: text,\n    chatId: $json.chatId,\n    messageId: ''\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "e447d8ab-4e99-4e47-8add-5a6ae9003a55",
      "name": "Send Reply",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2992,
        528
      ],
      "parameters": {
        "text": "={{ $json.replyText }}",
        "chatId": "={{ $json.chatId }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "a714101a-c2c2-4ab8-a231-f7d7231217c5",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        640
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 336,
        "content": "## Step 2 \u2014 Credentials\n\nCreate and connect these credentials before testing:\n\n- Google Drive OAuth2 account \u2014 used by `Google Drive Trigger` and `Download Drive File`\n- Google Docs OAuth2 account \u2014 used by `Get Google Doc`\n- Google Sheets OAuth2 account \u2014 used by `Append Row in Sheet`\n- Telegram credential \u2014 used by `Send Reply` and `Send Unsupported Message`\n- HTTP Header Auth credential for NVIDIA NIM \u2014 set `Authorization: Bearer YOUR_API_KEY` and apply it to both NVIDIA HTTP Request nodes\n\nAfter connecting credentials, re-open each node once and confirm the correct account is selected."
      },
      "typeVersion": 1
    },
    {
      "id": "44bc95df-dcdc-404a-8ac6-08172e47e46e",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2720,
        800
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 448,
        "content": "## Step 4 \u2014 Activate\n\nBefore activating the workflow, test one file at a time in this order:\n\n- PDF\n- text file\n- CSV\n- image\n- Google Doc\n\nFor each test, confirm:\n- the correct branch ran\n- extracted text was produced\n- structured output was returned\n- a row was appended to `Extract_Log`\n- the Telegram reply was sent successfully\n\nActivate the workflow only after all supported file paths pass at least one full end-to-end test."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Route Input": {
      "main": [
        [
          {
            "node": "Download Drive File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download Drive File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get Google Doc",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download Drive File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Download Drive File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Unsupported Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize CSV": {
      "main": [
        [
          {
            "node": "Build Structuring Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize PDF": {
      "main": [
        [
          {
            "node": "Build Structuring Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Google Doc": {
      "main": [
        [
          {
            "node": "Normalize Google Doc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Image": {
      "main": [
        [
          {
            "node": "Build Structuring Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Input": {
      "main": [
        [
          {
            "node": "Route Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from CSV": {
      "main": [
        [
          {
            "node": "Normalize CSV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from PDF": {
      "main": [
        [
          {
            "node": "Normalize PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Raw Text": {
      "main": [
        [
          {
            "node": "Build Structuring Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Image Payload": {
      "main": [
        [
          {
            "node": "Analyze Image with NVIDIA",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Drive File": {
      "main": [
        [
          {
            "node": "Route Downloaded File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Text File": {
      "main": [
        [
          {
            "node": "Build Structuring Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Telegram Reply": {
      "main": [
        [
          {
            "node": "Send Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Drive Trigger": {
      "main": [
        [
          {
            "node": "Normalize Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Google Doc": {
      "main": [
        [
          {
            "node": "Build Structuring Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Downloaded File": {
      "main": [
        [
          {
            "node": "Extract from PDF",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Image Data URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Extract from Text File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Extract from CSV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from Text File": {
      "main": [
        [
          {
            "node": "Normalize Text File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Image Data URL": {
      "main": [
        [
          {
            "node": "Build Image Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Structured Output": {
      "main": [
        [
          {
            "node": "Append Row in Sheet",
            "type": "main",
            "index": 0
          },
          {
            "node": "Build Telegram Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Image with NVIDIA": {
      "main": [
        [
          {
            "node": "Normalize Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Structuring Payload": {
      "main": [
        [
          {
            "node": "Structure Output with NVIDIA",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structure Output with NVIDIA": {
      "main": [
        [
          {
            "node": "Parse Structured Output",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This template monitors Google Drive folder for new files, extracts text from PDFs, images, text files, CSVs, and Google Docs., reads images with meta/llama-3.2-11b-vision-instruct, structures the result with nvidia/llama-3.3-nemotron-super-49b-v1.5, logs everything to Google…

Source: https://n8n.io/workflows/14188/ — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

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

Slack & Telegram

Automatically transform any website URL into a complete portfolio entry with professional screenshots and AI-generated Upwork project descriptions. Freelancers building their Upwork/portfolio from pas

HTTP Request, Google Drive, Google Sheets +2
Slack & Telegram

google drive to instagram, tiktok and youtube. Uses googleDriveTrigger, googleDrive, errorTrigger, telegram. Event-driven trigger; 15 nodes.

Google Drive Trigger, Google Drive, Error Trigger +5
Slack & Telegram

checkProcess(old). Uses googleSheets, httpRequest, telegram, @n-octo-n/n8n-nodes-json-database. Event-driven trigger; 40 nodes.

Google Sheets, HTTP Request, Telegram +3
Slack & Telegram

Type in Slack. Walk away. Get a professional PDF report and a structured Excel fix sheet delivered to Google Drive and posted back in your Slack thread — fully automated, zero manual work.

Compression, HTTP Request, Google Drive +3
Slack & Telegram

This workflow provides a complete solution for handling Telegram Stars payments, invoicing and refunds using n8n. It automates the process of sending invoices, managing pre-checkout approvals, recordi

HTTP Request, Execute Workflow Trigger, Google Sheets +2