AutomationFlowsGeneral › Extract Construction Blueprint Data with Vlm Run and Microsoft Office Suite

Extract Construction Blueprint Data with Vlm Run and Microsoft Office Suite

ByShahrear @shahrear on n8n.io

Automatically process Construction Blueprints into structured Excel entries with VLM extraction

Event trigger★★★☆☆ complexity8 nodes@Vlm Run/N8N Nodes VlmrunMicrosoft One Drive TriggerMicrosoft One DriveMicrosoft Excel
General Trigger: Event Nodes: 8 Complexity: ★★★☆☆ Added:

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

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": "53f75ca8-6a2a-46b2-b4f7-b4786170d8ce",
      "name": "VLM Run Parsing",
      "type": "@vlm-run/n8n-nodes-vlmrun.vlmRun",
      "position": [
        1728,
        288
      ],
      "parameters": {
        "domain": "construction.blueprint"
      },
      "credentials": {
        "vlmRunApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "fcf3a39e-e959-4c1b-8b9e-21e2553bd7dc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 768,
        "content": "# Construction Blueprint Processing with VLM Run\n\nAutomatically extracts structured construction blueprint details from uploaded documents in OneDrive and saves them into an Excel Sheet for tracking, compliance, or reporting.\n\n## Workflow\n\n1. \ud83d\udcc2 Detect file upload in OneDrive\n2. \u2b07\ufe0f Download the uploaded document\n3. \ud83e\udd16 Convert document to structured text using VLM Run (`construction.blueprint`)\n4. \ud83d\udcca Append extracted order data to Excel Sheet\n\n## Perfect for\n\n* Construction blueprint processing\n* Architectural plan reviews\n* Engineering drawing requests\n* Permit and regulatory submission workflows\n* Automated compliance documentation\n\n## Requirements\n\n* VLM Run API access\n* OneDrive + Excel OAuth2\n* n8n server with active workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "d4229b1b-5209-46f7-b2de-1ad7cbb096ef",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2048,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 768,
        "content": "# Append Row in Excel Sheet\n\n**Function:** Appends extracted structured data into an Excel Sheet.\n\n* Columns could be: Project Details, Document Type, Document Number, Issue Date, Author's Name, Drawing Title Numbers, Revision History, Annotations Markups, Job Name\n\n**Benefit:** Provides a structured, continuously updated database for tracking blueprints\n"
      },
      "typeVersion": 1
    },
    {
      "id": "a4ced382-369a-4694-9da8-2cbc4b15c690",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1024,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 768,
        "content": "# \ud83d\udcc1 Input Processing\n\n**Monitors & downloads blueprint files from OneDrive.**\n\n**Process:**\n1. Watches designated Drive folder\n2. Auto-triggers on new uploads\n3. Downloads files for AI processing\n\n**Supported Formats:**\n- Images (JPG, PNG, WEBP)\n- PDF documents\n- Mobile camera uploads\n- Scanned receipts"
      },
      "typeVersion": 1
    },
    {
      "id": "1956b501-857a-411e-a151-c67a487a1e54",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1536,
        -208
      ],
      "parameters": {
        "width": 480,
        "height": 768,
        "content": "# VLM Run (Document)\n\n**Function:** Sends the blueprint file to VLM Run under the category `construction.blueprint`.\n\n* Extracts structured details such as:\n\n  * Project name, address, permit ID\n  * Drawing elements (dimensions, materials, quantities, compliance flags)\n  * Architect/engineer details (name, license number, approval date)\n\n**Benefit:** Turns complex construction blueprints into machine-readable JSON\n"
      },
      "typeVersion": 1
    },
    {
      "id": "90bb7989-ea88-4ae4-95c2-ccf9a3040b33",
      "name": "Microsoft OneDrive Trigger",
      "type": "n8n-nodes-base.microsoftOneDriveTrigger",
      "position": [
        1104,
        288
      ],
      "parameters": {
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "AE5A9F7C8F06E9ED!se2bce082634d472487eaa7baa7be36b1"
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "watchFolder": true
      },
      "credentials": {
        "microsoftOneDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "579975f5-effe-4cb8-8358-f877ba99d414",
      "name": "Download a file",
      "type": "n8n-nodes-base.microsoftOneDrive",
      "position": [
        1312,
        288
      ],
      "parameters": {
        "fileId": "={{ $json.id }}",
        "operation": "download"
      },
      "credentials": {
        "microsoftOneDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "dad380a9-0be1-465c-99d5-f326e608100a",
      "name": "Append data to sheet",
      "type": "n8n-nodes-base.microsoftExcel",
      "position": [
        2224,
        288
      ],
      "parameters": {
        "options": {},
        "fieldsUi": {
          "values": [
            {
              "column": "PROJECT DETAILS",
              "fieldValue": "={{ (v => {\n  const S = x =>\n    x == null ? '' :\n    Array.isArray(x)\n      ? x.map(S).map(s => String(s).trim()).filter(Boolean).join(',\\n')\n      : typeof x === 'object'\n        ? Object.entries(x)\n            .filter(([_, val]) => {\n              if (val == null) return false\n              if (typeof val === 'string') return val.trim() !== ''\n              if (Array.isArray(val)) return val.map(S).some(s => String(s).trim() !== '')\n              if (typeof val === 'object') return Object.keys(val).length > 0\n              return true\n            })\n            .map(([k, val]) => {\n              const sv = S(val)\n              return sv ? `${k}: ${sv}` : ''\n            })\n            .filter(Boolean)\n            .join(',\\n')\n        : String(x).trim()\n  return S(v)\n})($json.response?.project_details) }}\n"
            },
            {
              "column": "DOCUMENT TYPE",
              "fieldValue": "={{ $json.response.document_metadata.document_type }}"
            },
            {
              "column": "DOCUMENT NUMBER",
              "fieldValue": "={{ $json.response.document_metadata.document_number }}"
            },
            {
              "column": "ISSUE DATE",
              "fieldValue": "={{ $json.response.document_metadata.issue_date }}"
            },
            {
              "column": "AUTHOR'S NAME",
              "fieldValue": "={{ (v => {\n  const S = x =>\n    x == null ? '' :\n    Array.isArray(x)\n      ? x.map(S).map(s => String(s).trim()).filter(Boolean).join(',\\n')\n      : typeof x === 'object'\n        ? Object.entries(x)\n            .filter(([_, val]) => {\n              if (val == null) return false\n              if (typeof val === 'string') return val.trim() !== ''\n              if (Array.isArray(val)) return val.map(S).some(s => String(s).trim() !== '')\n              if (typeof val === 'object') return Object.keys(val).length > 0\n              return true\n            })\n            .map(([k, val]) => {\n              const sv = S(val)\n              return sv ? `${k}: ${sv}` : ''\n            })\n            .filter(Boolean)\n            .join(',\\n')\n        : String(x).trim()\n  return S(v)\n})($json.response?.document_metadata?.author) }}\n"
            },
            {
              "column": "DRAWING TITLE NUMBERS",
              "fieldValue": "={{ (v => {\n  const S = x =>\n    x == null ? '' :\n    Array.isArray(x)\n      ? x.map(S).map(s => String(s).trim()).filter(Boolean).join(',\\n')\n      : typeof x === 'object'\n        ? Object.entries(x)\n            .filter(([_, val]) => {\n              if (val == null) return false\n              if (typeof val === 'string') return val.trim() !== ''\n              if (Array.isArray(val)) return val.map(S).some(s => String(s).trim() !== '')\n              if (typeof val === 'object') return Object.keys(val).length > 0\n              return true\n            })\n            .map(([k, val]) => {\n              const sv = S(val)\n              return sv ? `${k}: ${sv}` : ''\n            })\n            .filter(Boolean)\n            .join(',\\n')\n        : String(x).trim()\n  return S(v)\n})($json.response?.drawings_blueprints?.drawing_titles_numbers) }}\n"
            },
            {
              "column": "SCALE LEGENDS",
              "fieldValue": "={{ $json.response.drawings_blueprints.scale_legends }}"
            },
            {
              "column": "REVISION HISTORY",
              "fieldValue": "={{ (v => {\n  const S = x =>\n    x == null ? '' :\n    Array.isArray(x)\n      ? x.map(S).map(s => String(s).trim()).filter(Boolean).join(',\\n')\n      : typeof x === 'object'\n        ? Object.entries(x)\n            .filter(([_, val]) => {\n              if (val == null) return false\n              if (typeof val === 'string') return val.trim() !== ''\n              if (Array.isArray(val)) return val.map(S).some(s => String(s).trim() !== '')\n              if (typeof val === 'object') return Object.keys(val).length > 0\n              return true\n            })\n            .map(([k, val]) => {\n              const sv = S(val)\n              return sv ? `${k}: ${sv}` : ''\n            })\n            .filter(Boolean)\n            .join(',\\n')\n        : String(x).trim()\n  return S(v)\n})($json.response?.drawings_blueprints?.revision_history) }}\n"
            },
            {
              "column": "ANNOTATIONS MARKUPS",
              "fieldValue": "={{ (v => {\n  const S = x =>\n    x == null ? '' :\n    Array.isArray(x)\n      ? x.map(S).map(s => String(s).trim()).filter(Boolean).join(',\\n')\n      : typeof x === 'object'\n        ? Object.entries(x)\n            .filter(([_, val]) => {\n              if (val == null) return false\n              if (typeof val === 'string') return val.trim() !== ''\n              if (Array.isArray(val)) return val.map(S).some(s => String(s).trim() !== '')\n              if (typeof val === 'object') return Object.keys(val).length > 0\n              return true\n            })\n            .map(([k, val]) => {\n              const sv = S(val)\n              return sv ? `${k}: ${sv}` : ''\n            })\n            .filter(Boolean)\n            .join(',\\n')\n        : String(x).trim()\n  return S(v)\n})($json.response?.drawings_blueprints?.annotations_markups) }}\n"
            },
            {
              "column": "JOB NAME",
              "fieldValue": "={{ $json.response.title_block.job_name }}"
            },
            {
              "column": "ADDRESS",
              "fieldValue": "={{ (v => {\n  const S = x =>\n    x == null ? '' :\n    Array.isArray(x)\n      ? x.map(S).map(s => String(s).trim()).filter(Boolean).join(',\\n')\n      : typeof x === 'object'\n        ? Object.entries(x)\n            .filter(([_, val]) => {\n              if (val == null) return false\n              if (typeof val === 'string') return val.trim() !== ''\n              if (Array.isArray(val)) return val.map(S).some(s => String(s).trim() !== '')\n              if (typeof val === 'object') return Object.keys(val).length > 0\n              return true\n            })\n            .map(([k, val]) => {\n              const sv = S(val)\n              return sv ? `${k}: ${sv}` : ''\n            })\n            .filter(Boolean)\n            .join(',\\n')\n        : String(x).trim()\n  return S(v)\n})($json.response?.title_block?.address) }}\n"
            },
            {
              "column": "DRAWING NUMBER",
              "fieldValue": "={{ $json.response.title_block.drawing_number }}"
            },
            {
              "column": "REVISION",
              "fieldValue": "={{ $json.response.title_block.revision }}"
            },
            {
              "column": "DRAWN BY",
              "fieldValue": "={{ $json.response.title_block.drawn_by }}"
            },
            {
              "column": "CHECKED BY",
              "fieldValue": "={{ $json.response.title_block.checked_by }}"
            },
            {
              "column": "SCALE",
              "fieldValue": "={{ $json.response.title_block.scale }}"
            },
            {
              "column": "AGENCY NAME",
              "fieldValue": "={{ $json.response.title_block.agency_name }}"
            },
            {
              "column": "DOCUMENT TITLE",
              "fieldValue": "={{ $json.response.title_block.document_title }}"
            },
            {
              "column": "OTHER METADATA",
              "fieldValue": "={{ (v => {\n  const S = x =>\n    x == null ? '' :\n    Array.isArray(x)\n      ? x.map(S).map(s => String(s).trim()).filter(Boolean).join(',\\n')\n      : typeof x === 'object'\n        ? Object.entries(x)\n            .filter(([_, val]) => {\n              if (val == null) return false\n              if (typeof val === 'string') return val.trim() !== ''\n              if (Array.isArray(val)) return val.map(S).some(s => String(s).trim() !== '')\n              if (typeof val === 'object') return Object.keys(val).length > 0\n              return true\n            })\n            .map(([k, val]) => {\n              const sv = S(val)\n              return sv ? `${k}: ${sv}` : ''\n            })\n            .filter(Boolean)\n            .join(',\\n')\n        : String(x).trim()\n  return S(v)\n})($json.response?.title_block?.other_metadata) }}\n"
            },
            {
              "column": "DRAWING TYPE",
              "fieldValue": "={{ $json.response.drawing_type }}"
            },
            {
              "column": "LEGAL COMPLIANCE",
              "fieldValue": "={{ (v => {\n  const S = x =>\n    x == null ? '' :\n    Array.isArray(x)\n      ? x.map(S).map(s => String(s).trim()).filter(Boolean).join(',\\n')\n      : typeof x === 'object'\n        ? Object.entries(x)\n            .filter(([_, val]) => {\n              if (val == null) return false\n              if (typeof val === 'string') return val.trim() !== ''\n              if (Array.isArray(val)) return val.map(S).some(s => String(s).trim() !== '')\n              if (typeof val === 'object') return Object.keys(val).length > 0\n              return true\n            })\n            .map(([k, val]) => {\n              const sv = S(val)\n              return sv ? `${k}: ${sv}` : ''\n            })\n            .filter(Boolean)\n            .join(',\\n')\n        : String(x)\n  return S(v)\n})($json.response?.compliance_legal) }}\n"
            },
            {
              "column": "SCALE INFORMATION",
              "fieldValue": "={{ $json.response.scale_information }}"
            }
          ]
        },
        "resource": "worksheet",
        "workbook": {
          "__rl": true,
          "mode": "list",
          "value": "AE5A9F7C8F06E9ED!sc46f0642e32643ada075b983e522bdc7",
          "cachedResultUrl": "https://onedrive.live.com/personal/ae5a9f7c8f06e9ed/_layouts/15/doc.aspx?resid=c46f0642-e326-43ad-a075-b983e522bdc7&cid=ae5a9f7c8f06e9ed",
          "cachedResultName": "Construction Blueprint"
        },
        "operation": "append",
        "worksheet": {
          "__rl": true,
          "mode": "list",
          "value": "{+1234567890+1234567890}",
          "cachedResultUrl": "https://onedrive.live.com/personal/ae5a9f7c8f06e9ed/_layouts/15/doc.aspx?resid=c46f0642-e326-43ad-a075-b983e522bdc7&cid=ae5a9f7c8f06e9ed&activeCell=Sheet1!A1",
          "cachedResultName": "Sheet1"
        }
      },
      "credentials": {
        "microsoftExcelOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    }
  ],
  "connections": {
    "Download a file": {
      "main": [
        [
          {
            "node": "VLM Run Parsing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "VLM Run Parsing": {
      "main": [
        [
          {
            "node": "Append data to sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Microsoft OneDrive Trigger": {
      "main": [
        [
          {
            "node": "Download a file",
            "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

Automatically process Construction Blueprints into structured Excel entries with VLM extraction

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

More General workflows → · Browse all categories →

Related workflows

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

General

This workflow is perfect for users who need a reliable, automated way to transfer and organize data from OneDrive into Excel—especially for tasks like portfolio tracking, inventory management, and rec

Microsoft One Drive Trigger, Stop And Error, Microsoft One Drive +1
General

This workflow contains community nodes that are only compatible with the self-hosted version of n8n. It creates a nested folder in your OneDrive's My Files You could be creating folders like:

Execute Workflow Trigger, Microsoft One Drive, N8N Nodes Datastore
General

This workflow contains community nodes that are only compatible with the self-hosted version of n8n. Monitors Google Drive for new video file uploads Downloads and processes videos using VLM Run AI tr

@Vlm Run/N8N Nodes Vlmrun, Google Drive Trigger, Google Drive +1
General

Create A Folder In Onedrive. Uses manualTrigger, microsoftOneDrive. Event-driven trigger; 2 nodes.

Microsoft One Drive
General

Get All Excel Workbooks. Uses manualTrigger, microsoftExcel. Event-driven trigger; 2 nodes.

Microsoft Excel