This workflow corresponds to n8n.io template #14274 — 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 →
{
"nodes": [
{
"id": "233a882a-a1bb-4b03-8be1-eca89fc84e1e",
"name": "Document Upload Form",
"type": "n8n-nodes-base.formTrigger",
"position": [
-1632,
96
],
"parameters": {
"options": {
"appendAttribution": false
},
"formTitle": "Tax Document Upload",
"formFields": {
"values": [
{
"fieldType": "file",
"fieldLabel": "Select Document",
"multipleFiles": false,
"requiredField": true,
"acceptFileTypes": ".pdf, .png, .jpg, .jpeg"
}
]
},
"formDescription": "Upload tax-related documents (invoices, 1099, W-2, receipts) for automated processing"
},
"typeVersion": 2.3
},
{
"id": "bb4394e9-a3f7-40a7-8481-5fbea0378c30",
"name": "Webhook Upload",
"type": "n8n-nodes-base.webhook",
"position": [
-1632,
288
],
"parameters": {
"path": "document-upload",
"options": {
"rawBody": true
},
"httpMethod": "POST",
"responseMode": "lastNode"
},
"typeVersion": 2.1
},
{
"id": "a66491fa-d58f-48b5-8821-3ac28abc46e7",
"name": "Workflow Configuration",
"type": "n8n-nodes-base.set",
"position": [
-1344,
208
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "maxFileSizeMB",
"type": "number",
"value": 50
},
{
"id": "id-2",
"name": "allowedMimeTypes",
"type": "string",
"value": "application/pdf,image/png,image/jpeg"
},
{
"id": "id-3",
"name": "storageBasePath",
"type": "string",
"value": "/tmp/tax-documents"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "81c37b00-369b-4699-9a8e-af855ed2c9d3",
"name": "Check File Size",
"type": "n8n-nodes-base.if",
"position": [
-1120,
208
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "id-1",
"operator": {
"type": "number",
"operation": "lte"
},
"leftValue": "={{ $binary.data.fileSize }}",
"rightValue": "={{ $('Workflow Configuration').item.json.maxFileSizeMB * 1024 * 1024 }}"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "92052172-2490-49bc-89cf-1c8105c04178",
"name": "Check MIME Type",
"type": "n8n-nodes-base.if",
"position": [
-864,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "id-1",
"operator": {
"type": "array",
"operation": "contains"
},
"leftValue": "={{ $('Workflow Configuration').item.json.allowedMimeTypes }}",
"rightValue": "={{ $binary.data.mimeType }}"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "17d16b6e-1923-4281-9e24-9db84d894942",
"name": "Generate Document Metadata",
"type": "n8n-nodes-base.code",
"position": [
-640,
-16
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const crypto = require('crypto');\n\n// Get binary data from the uploaded file\nconst binaryData = $binary.data;\nconst buffer = Buffer.from(binaryData.data, 'base64');\n\n// Calculate SHA-256 hash of file content\nconst hash = crypto.createHash('sha256').update(buffer).digest('hex');\n\n// Generate UUID for document_id\nconst documentId = crypto.randomUUID();\n\n// Extract file metadata\nconst fileName = binaryData.fileName || 'unknown';\nconst fileSize = buffer.length;\nconst mimeType = binaryData.mimeType || 'application/octet-stream';\n\n// Get current timestamp\nconst uploadedAt = new Date().toISOString();\n\n// Determine source (form or webhook)\nconst source = $node.name.includes('Form') ? 'form' : 'webhook';\n\n// Return metadata and preserve binary data\nreturn {\n json: {\n document_id: documentId,\n file_name: fileName,\n file_size: fileSize,\n mime_type: mimeType,\n file_hash: hash,\n uploaded_at: uploadedAt,\n source: source\n },\n binary: {\n data: binaryData\n }\n};"
},
"typeVersion": 2
},
{
"id": "129736a4-2406-499f-b776-e7423c12f528",
"name": "Check for Duplicate",
"type": "n8n-nodes-base.postgres",
"position": [
-480,
-16
],
"parameters": {
"query": "SELECT document_id, status FROM documents WHERE file_hash = $1 LIMIT 1",
"options": {
"queryReplacement": "={{ $json.file_hash }}"
},
"operation": "executeQuery"
},
"typeVersion": 2.6
},
{
"id": "e3d4134d-83fc-4c11-8066-d321e28e9e79",
"name": "Is Duplicate?",
"type": "n8n-nodes-base.if",
"position": [
-224,
0
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "id-1",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $('Check for Duplicate').item.json.length }}",
"rightValue": "0"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "1e2aab77-6bad-4d17-b966-c1f86f85e15c",
"name": "Insert Document Record",
"type": "n8n-nodes-base.postgres",
"position": [
16,
320
],
"parameters": {
"query": "INSERT INTO documents (document_id, file_hash, file_name, file_size, mime_type, source, status, created_at) VALUES ($1, $2, $3, $4, $5, $6, 'received', NOW()) RETURNING *",
"options": {
"queryReplacement": "={{ $('Generate Document Metadata').item.json.document_id }},={{ $('Generate Document Metadata').item.json.file_hash }},={{ $('Generate Document Metadata').item.json.file_name }},={{ $('Generate Document Metadata').item.json.file_size }},={{ $('Generate Document Metadata').item.json.mime_type }},={{ $('Generate Document Metadata').item.json.source }}"
},
"operation": "executeQuery"
},
"typeVersion": 2.6
},
{
"id": "2a297f88-0611-4429-8539-7ab3c0eebaf0",
"name": "Return Success Response",
"type": "n8n-nodes-base.set",
"position": [
192,
320
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "status",
"type": "string",
"value": "success"
},
{
"id": "id-2",
"name": "message",
"type": "string",
"value": "Document uploaded successfully"
},
{
"id": "id-3",
"name": "document_id",
"type": "string",
"value": "={{ $('Generate Document Metadata').item.json.document_id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "1cad7e0a-a26f-4b72-aa60-54474755e0a5",
"name": "Return Duplicate Response",
"type": "n8n-nodes-base.set",
"position": [
96,
-128
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "status",
"type": "string",
"value": "duplicate"
},
{
"id": "id-2",
"name": "message",
"type": "string",
"value": "Document already exists (duplicate detected via file hash)"
},
{
"id": "id-3",
"name": "existing_document_id",
"type": "string",
"value": "={{ $('Check for Duplicate').item.json[0].document_id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "d6807296-11b5-4172-9b57-7cb60f8c48e9",
"name": "Return Validation Error",
"type": "n8n-nodes-base.set",
"position": [
-656,
400
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "status",
"type": "string",
"value": "error"
},
{
"id": "id-2",
"name": "message",
"type": "string",
"value": "File validation failed: invalid size or file type"
},
{
"id": "id-3",
"name": "allowed_types",
"type": "string",
"value": "PDF, PNG, JPEG"
},
{
"id": "id-4",
"name": "max_size_mb",
"type": "string",
"value": "={{ $('Workflow Configuration').item.json.maxFileSizeMB }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "0276b453-0b64-4acf-8b78-e0ef46e9a70c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1696,
-64
],
"parameters": {
"color": 7,
"width": 288,
"height": 544,
"content": "## Upload Input\nAccepts document via form or webhook upload."
},
"typeVersion": 1
},
{
"id": "c1796c06-6915-4d94-9217-4536a44c2369",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1376,
64
],
"parameters": {
"color": 7,
"width": 448,
"height": 368,
"content": "## Configuration and Validation\nDefines file size limits, allowed types, and storage settings and Checks file size and MIME type before processing."
},
"typeVersion": 1
},
{
"id": "be61a78f-6df3-43a4-bc7e-cdb06c983faa",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-752,
320
],
"parameters": {
"color": 7,
"width": 320,
"height": 320,
"content": "## Error Handling\nReturns error if file validation fails."
},
"typeVersion": 1
},
{
"id": "64a28d6c-b407-4384-acc8-ca034e0f3dc8",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-704,
-176
],
"parameters": {
"color": 7,
"width": 352,
"height": 352,
"content": "## Metadata Generation and Duplicate check \nCreates document ID, hash, and extracts file details and Checks if file already exists using hash comparison."
},
"typeVersion": 1
},
{
"id": "048bb137-e535-4c66-ae29-e6ebf2328235",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-320,
-144
],
"parameters": {
"color": 7,
"width": 288,
"height": 320,
"content": "## Duplicate Handling\nReturns response if duplicate document is detected."
},
"typeVersion": 1
},
{
"id": "1cbae323-7c3e-4d0e-a77b-33ff6ea437fd",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
32,
-272
],
"parameters": {
"color": 7,
"width": 320,
"height": 320,
"content": "## Duplicate Handling\nReturns response if duplicate document is detected."
},
"typeVersion": 1
},
{
"id": "55a52db2-941c-426e-839d-d758dd527017",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-16,
144
],
"parameters": {
"color": 7,
"width": 384,
"height": 384,
"content": "## Success Response and Record Storage\nStores document metadata in Postgres database and \nReturns success message with document ID."
},
"typeVersion": 1
},
{
"id": "eec948bd-c212-4769-9b3d-3c51ea86ac29",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2288,
16
],
"parameters": {
"width": 464,
"height": 576,
"content": "## How it works\nThis workflow handles secure document uploads from a form or webhook.\n\nUploaded files are first validated for size and type to ensure they meet defined limits. Once validated, the workflow generates metadata including a unique document ID, file hash, and file details.\n\nThe file hash is used to check for duplicates in the database. If a duplicate is found, the workflow returns an existing record instead of storing it again.\n\nIf the file is new, its metadata is stored in Postgres with a \u201creceived\u201d status. A success response is returned with the document ID for tracking.\n\n## Setup\n1. Configure form or webhook for uploads \n2. Set max file size and allowed types \n3. Connect Postgres database \n4. Create documents table \n5. Test with sample uploads \n6. Activate the workflow"
},
"typeVersion": 1
}
],
"connections": {
"Is Duplicate?": {
"main": [
[
{
"node": "Return Duplicate Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Insert Document Record",
"type": "main",
"index": 0
}
]
]
},
"Webhook Upload": {
"main": [
[
{
"node": "Workflow Configuration",
"type": "main",
"index": 0
}
]
]
},
"Check File Size": {
"main": [
[
{
"node": "Check MIME Type",
"type": "main",
"index": 0
}
],
[
{
"node": "Return Validation Error",
"type": "main",
"index": 0
}
]
]
},
"Check MIME Type": {
"main": [
[
{
"node": "Generate Document Metadata",
"type": "main",
"index": 0
}
],
[
{
"node": "Return Validation Error",
"type": "main",
"index": 0
}
]
]
},
"Check for Duplicate": {
"main": [
[
{
"node": "Is Duplicate?",
"type": "main",
"index": 0
}
]
]
},
"Document Upload Form": {
"main": [
[
{
"node": "Workflow Configuration",
"type": "main",
"index": 0
}
]
]
},
"Insert Document Record": {
"main": [
[
{
"node": "Return Success Response",
"type": "main",
"index": 0
}
]
]
},
"Workflow Configuration": {
"main": [
[
{
"node": "Check File Size",
"type": "main",
"index": 0
}
]
]
},
"Generate Document Metadata": {
"main": [
[
{
"node": "Check for Duplicate",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow provides a reliable and secure system for uploading and managing documents.
Source: https://n8n.io/workflows/14274/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow acts as a junior finance research analyst for a UK boutique M&A or corporate finance team. It listens for Slack messages, classifies the request, gathers company or market data, and prod
Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.
Cancelacion_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 46 nodes.
Components. Uses postgres, readWriteFile. Event-driven trigger; 42 nodes.
This N8N workflow is designed to enrich seller data stored in a Postgres database by performing automated Google search lookups. It uses Bright Data's Web Unlocker to bypass search result restrictions