AutomationFlowsSlack & Telegram › Detect and Move Duplicate Google Drive Files with Supabase and Slack

Detect and Move Duplicate Google Drive Files with Supabase and Slack

ByShashwat Singh @shashwatsingh on n8n.io

This workflow automatically detects duplicate files uploaded to a specific Google Drive folder by generating an MD5 hash of each file and comparing it against a Supabase database. If a duplicate is found, the file is moved to a dedicated Duplicates folder and a Slack…

Event trigger★★★★☆ complexity27 nodesGoogle Drive TriggerGoogle DriveCryptoSupabaseSlack
Slack & Telegram Trigger: Event Nodes: 27 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #13534 — 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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "trigger-1",
      "name": "Google Drive Trigger",
      "type": "n8n-nodes-base.googleDriveTrigger",
      "position": [
        0,
        -160
      ],
      "parameters": {
        "event": "fileCreated",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "mode": "list",
          "value": "1vleObg6ZBzUflpAVT2Joc5D7oBNYWNUw",
          "cachedResultUrl": "https://drive.google.com/drive/folders/1vleObg6ZBzUflpAVT2Joc5D7oBNYWNUw",
          "cachedResultName": "Lendium Files"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "code-prepare",
      "name": "Prepare File Info",
      "type": "n8n-nodes-base.code",
      "position": [
        224,
        -160
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Extract and normalize file metadata from Google Drive Trigger\nconst triggerData = $input.item.json;\n\nconst fileId = triggerData.id || triggerData.fileId || '';\nconst fileName = triggerData.name || triggerData.title || 'unknown';\nconst mimeType = triggerData.mimeType || triggerData.mime_type || '';\nconst fileSize = triggerData.size || triggerData.fileSize || 0;\nconst createdTime = triggerData.createdTime || triggerData.createdDate || new Date().toISOString();\n\nreturn {\n  json: {\n    fileId,\n    fileName,\n    mimeType,\n    fileSize: parseInt(fileSize, 10) || 0,\n    createdTime,\n    originalData: triggerData\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "gdrive-download",
      "name": "Download File",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        448,
        -160
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.fileId }}"
        },
        "options": {},
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "if-validate-binary",
      "name": "Validate Binary",
      "type": "n8n-nodes-base.if",
      "position": [
        672,
        -160
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "binary-check",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ !!$binary && !!$binary.data }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "crypto-hash",
      "name": "Crypto",
      "type": "n8n-nodes-base.crypto",
      "position": [
        896,
        -256
      ],
      "parameters": {
        "binaryData": true,
        "dataPropertyName": "hash"
      },
      "typeVersion": 1
    },
    {
      "id": "supa-check-hash",
      "name": "Check Hash Exists",
      "type": "n8n-nodes-base.supabase",
      "position": [
        1120,
        -256
      ],
      "parameters": {
        "limit": 1,
        "filters": {
          "conditions": [
            {
              "keyName": "hash",
              "keyValue": "={{ $json.hash }}",
              "condition": "eq"
            }
          ]
        },
        "tableId": "file_hashes",
        "matchType": "allFilters",
        "operation": "getAll"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "gdrive-move-dup",
      "name": "Move to Duplicates",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1792,
        -256
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Prepare File Info').item.json.fileId }}"
        },
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive",
          "cachedResultUrl": "https://drive.google.com/drive/my-drive",
          "cachedResultName": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "17_l9LfnpyJas6iZy3sIMNNdfi4W7ia6o",
          "cachedResultUrl": "https://drive.google.com/drive/folders/17_l9LfnpyJas6iZy3sIMNNdfi4W7ia6o",
          "cachedResultName": "Duplicates"
        },
        "operation": "move"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "slack-notify",
      "name": "Notify Duplicate",
      "type": "n8n-nodes-base.slack",
      "position": [
        2016,
        -256
      ],
      "parameters": {
        "text": "=\ud83d\udd34 *Duplicate File Detected*\n\n\ud83d\udcc4 *File:* {{ $('Prepare File Info').item.json.fileName }}\n\ud83c\udd94 *File ID:* {{ $('Prepare File Info').item.json.fileId }}\n\ud83d\udd11 *MD5 Hash:* {{ $('Crypto').item.json.hash }}\n\ud83d\udce6 *Size:* {{ $('Prepare File Info').item.json.fileSize }} bytes\n\ud83d\udcc2 *Action:* Moved to Duplicates folder\n\u23f0 *Time:* {{ $now.toISO() }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0ABU5WAKLH",
          "cachedResultName": "estateline-ai"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "code-prep-dup-log",
      "name": "Prepare Dup Log Data",
      "type": "n8n-nodes-base.code",
      "position": [
        2464,
        -256
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Prepare data for Supabase insert into dedup_audit_log\nreturn {\n  json: {\n    file_id: $('Prepare File Info').item.json.fileId,\n    file_name: $('Prepare File Info').item.json.fileName,\n    hash: $('Crypto').item.json.hash,\n    status: 'duplicate',\n    action_taken: 'moved_to_duplicates'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "supa-log-dup",
      "name": "Log Duplicate Event",
      "type": "n8n-nodes-base.supabase",
      "position": [
        2688,
        -256
      ],
      "parameters": {
        "tableId": "dedup_audit_log",
        "dataToSend": "autoMapInputData"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "code-prep-insert-hash",
      "name": "Prepare Hash Data",
      "type": "n8n-nodes-base.code",
      "position": [
        1792,
        96
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Prepare data for Supabase insert into file_hashes\nreturn {\n  json: {\n    file_id: $('Prepare File Info').item.json.fileId,\n    file_name: $('Prepare File Info').item.json.fileName,\n    hash: $('Crypto').item.json.hash,\n    file_size: $('Prepare File Info').item.json.fileSize,\n    mime_type: $('Prepare File Info').item.json.mimeType\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "supa-insert-hash",
      "name": "Insert New Hash",
      "type": "n8n-nodes-base.supabase",
      "position": [
        2016,
        96
      ],
      "parameters": {
        "tableId": "file_hashes",
        "dataToSend": "autoMapInputData"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "code-handle-db-error",
      "name": "Handle DB Error",
      "type": "n8n-nodes-base.code",
      "position": [
        2240,
        96
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Check if the insert was successful or if a conflict/error occurred\nconst item = $input.item.json;\n\nif (item.error) {\n  return {\n    json: {\n      status: 'duplicate_race_condition',\n      message: 'Concurrent upload detected - hash already exists',\n      error: item.error\n    }\n  };\n}\n\n// If the response doesn't contain an id, it may be a conflict\nif (!item.id && item.id !== 0) {\n  return {\n    json: {\n      status: 'duplicate_race_condition',\n      message: 'Hash already existed (conflict detected)',\n      fileId: $('Prepare File Info').item.json.fileId,\n      fileName: $('Prepare File Info').item.json.fileName\n    }\n  };\n}\n\nreturn {\n  json: {\n    status: 'unique_inserted',\n    insertedId: item.id,\n    fileId: $('Prepare File Info').item.json.fileId,\n    fileName: $('Prepare File Info').item.json.fileName\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "code-prep-unique-log",
      "name": "Prepare Unique Log Data",
      "type": "n8n-nodes-base.code",
      "position": [
        2464,
        96
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Prepare data for Supabase insert into dedup_audit_log\nconst status = $json.status;\nreturn {\n  json: {\n    file_id: $('Prepare File Info').item.json.fileId,\n    file_name: $('Prepare File Info').item.json.fileName,\n    hash: $('Crypto').item.json.hash,\n    status: status,\n    action_taken: status === 'unique_inserted' ? 'stored_hash' : 'race_condition_caught'\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "supa-log-unique",
      "name": "Log Unique Event",
      "type": "n8n-nodes-base.supabase",
      "position": [
        2688,
        96
      ],
      "parameters": {
        "tableId": "dedup_audit_log",
        "dataToSend": "autoMapInputData"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "code-missing-binary",
      "name": "Handle Missing Binary",
      "type": "n8n-nodes-base.code",
      "position": [
        880,
        -16
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const fileInfo = $('Prepare File Info').item.json;\n\nreturn {\n  json: {\n    status: 'error',\n    errorType: 'missing_binary',\n    message: `Binary data missing for file: ${fileInfo.fileName} (${fileInfo.fileId})`,\n    fileId: fileInfo.fileId,\n    fileName: fileInfo.fileName,\n    mimeType: fileInfo.mimeType,\n    timestamp: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "code-prep-error-log",
      "name": "Prepare Error Log Data",
      "type": "n8n-nodes-base.code",
      "position": [
        1088,
        -16
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Prepare data for Supabase insert into dedup_audit_log\nreturn {\n  json: {\n    file_id: $json.fileId,\n    file_name: $json.fileName,\n    hash: null,\n    status: 'error',\n    action_taken: 'none',\n    error_message: $json.message\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "supa-log-error",
      "name": "Log Error Event",
      "type": "n8n-nodes-base.supabase",
      "position": [
        1312,
        -16
      ],
      "parameters": {
        "tableId": "dedup_audit_log",
        "dataToSend": "autoMapInputData"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "83d46f79-7eb1-4915-b817-695fb32e4e73",
      "name": "No Operation, do nothing",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1568,
        -352
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "9728263e-e319-41bb-b6a7-fef61060399b",
      "name": "Same File Re-triggered?",
      "type": "n8n-nodes-base.if",
      "position": [
        1344,
        -256
      ],
      "parameters": {
        "options": {
          "ignoreCase": false
        },
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "dup-check",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.hash }}",
              "rightValue": 0
            },
            {
              "id": "b2521d34-e9bd-4b07-9333-fac03c8a64b0",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $('Check Hash Exists').item.json.file_id }}",
              "rightValue": "={{ $('Prepare File Info').item.json.fileId }}"
            }
          ]
        }
      },
      "typeVersion": 2.3,
      "alwaysOutputData": false
    },
    {
      "id": "if-duplicate",
      "name": "Hash Matched Different File?",
      "type": "n8n-nodes-base.if",
      "position": [
        1568,
        -144
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "dup-check",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.file_id }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.3,
      "alwaysOutputData": false
    },
    {
      "id": "5b469c4c-c982-4768-b5ed-25024cba109f",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -880
      ],
      "parameters": {
        "width": 656,
        "height": 544,
        "content": "This workflow detects and handles duplicate files uploaded to a specific Google Drive folder using content based hashing. Instead of relying on file names, it generates an MD5 hash from the actual file binary and compares it against a Supabase database.\n\nIf the same hash already exists for a different file, the new file is automatically moved to a Duplicates folder and a Slack alert is sent. If the file is unique, its hash is stored in Supabase. All outcomes, including duplicates, successful inserts, race conditions, and binary errors, are logged in an audit table.\n\n## How it works\n\n1. Watches a Google Drive folder for new files.\n2. Downloads the file and generates an MD5 hash.\n3. Checks Supabase for an existing hash match.\n4. Moves duplicate files and sends Slack alerts.\n5. Stores unique hashes and logs all events.\n\n## Setup steps\n\n1. Connect Google Drive and select the folder to monitor.\n2. Connect Supabase and create `file_hashes` and `dedup_audit_log` tables.\n3. Connect Slack and choose a notification channel.\n4. Set your Duplicates folder ID in the Move node."
      },
      "typeVersion": 1
    },
    {
      "id": "b692e01c-32b3-40fe-9087-7a241f052d05",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -272
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 352,
        "content": "Watches a specific Google Drive folder for new uploads, extracts metadata, downloads the file, and validates that binary data exists before hashing."
      },
      "typeVersion": 1
    },
    {
      "id": "fd0d975f-a6f4-496d-842b-1f04693c3540",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        848,
        -448
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 352,
        "content": "Generates an MD5 hash from the file binary and checks Supabase to determine whether the hash already exists."
      },
      "typeVersion": 1
    },
    {
      "id": "a8d32a63-100b-4768-8e25-65076003e777",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        -448
      ],
      "parameters": {
        "color": 7,
        "width": 864,
        "height": 352,
        "content": "If the hash exists for a different file, the workflow moves the file to a Duplicates folder and sends a Slack notification."
      },
      "typeVersion": 1
    },
    {
      "id": "728ec4a7-cac6-4d9b-836b-6f89f3b0bd66",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1760,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 272,
        "content": "If the hash does not exist, the workflow inserts the new hash into Supabase and confirms successful storage, including race condition handling."
      },
      "typeVersion": 1
    },
    {
      "id": "7341d2ae-00fa-4aaa-ade2-2626851c31b4",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2416,
        -432
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 720,
        "content": "Logs duplicates, unique inserts, race conditions, and binary errors into a Supabase audit table for traceability."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Crypto": {
      "main": [
        [
          {
            "node": "Check Hash Exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download File": {
      "main": [
        [
          {
            "node": "Validate Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle DB Error": {
      "main": [
        [
          {
            "node": "Prepare Unique Log Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert New Hash": {
      "main": [
        [
          {
            "node": "Handle DB Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Binary": {
      "main": [
        [
          {
            "node": "Crypto",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Handle Missing Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Duplicate": {
      "main": [
        [
          {
            "node": "Prepare Dup Log Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Hash Exists": {
      "main": [
        [
          {
            "node": "Same File Re-triggered?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare File Info": {
      "main": [
        [
          {
            "node": "Download File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Hash Data": {
      "main": [
        [
          {
            "node": "Insert New Hash",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Move to Duplicates": {
      "main": [
        [
          {
            "node": "Notify Duplicate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Drive Trigger": {
      "main": [
        [
          {
            "node": "Prepare File Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Dup Log Data": {
      "main": [
        [
          {
            "node": "Log Duplicate Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle Missing Binary": {
      "main": [
        [
          {
            "node": "Prepare Error Log Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Error Log Data": {
      "main": [
        [
          {
            "node": "Log Error Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Unique Log Data": {
      "main": [
        [
          {
            "node": "Log Unique Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Same File Re-triggered?": {
      "main": [
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Hash Matched Different File?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hash Matched Different File?": {
      "main": [
        [
          {
            "node": "Move to Duplicates",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Hash Data",
            "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 workflow automatically detects duplicate files uploaded to a specific Google Drive folder by generating an MD5 hash of each file and comparing it against a Supabase database. If a duplicate is found, the file is moved to a dedicated Duplicates folder and a Slack…

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

This workflow contains community nodes that are only compatible with the self-hosted version of n8n.

@Vlm Run/N8N Nodes Vlmrun, Google Drive Trigger, Google Drive +1
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 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 resu

Google Drive Trigger, Google Drive, Google Docs +3
Slack & Telegram

Upload a document (PDF, PNG, JPEG) via a web form and let easybits Extractor classify it into one of your defined categories. Based on the classification result and a confidence score, the document is

Form Trigger, HTTP Request, Google Drive +1
Slack & Telegram

This workflow automates the incident response lifecycle — from creation to communication and archival. It instantly creates Jira tickets for new incidents, alerts the on-call Slack team, generates tim

Jira, Slack, Google Sheets +1