AutomationFlowsSocial Media › Audit Youtube Video Metadata Changes with Google Sheets and Slack

Audit Youtube Video Metadata Changes with Google Sheets and Slack

ByKevin Yu @exekyute on n8n.io

This workflow runs daily to fetch all videos from a YouTube channel, compare key metadata fields against a prior snapshot stored in Google Sheets, log any differences, and optionally post a summary to Slack. Runs every day at 08:00 on a schedule. Reads your YouTube channel’s…

Cron / scheduled trigger★★★★☆ complexity21 nodesYouTubeGoogle SheetsSlack
Social Media Trigger: Cron / scheduled Nodes: 21 Complexity: ★★★★☆ Added:

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

This workflow follows the Google Sheets → Slack 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": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true,
    "templateCredsSetupCompleted": false
  },
  "name": "Audit YouTube video metadata changes against a saved snapshot",
  "tags": [],
  "nodes": [
    {
      "id": "b393d9be-6d43-4aeb-9153-c5ad18c0b793",
      "name": "Run Daily Audit",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        304
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "d9c5cd6a-ed15-4a3e-9013-c6cef3a69383",
      "name": "Set Audit Options",
      "type": "n8n-nodes-base.set",
      "position": [
        160,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "opt-channel",
              "name": "channelId",
              "type": "string",
              "value": ""
            },
            {
              "id": "opt-notify",
              "name": "alwaysNotify",
              "type": "boolean",
              "value": false
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "c757ff45-aadc-4226-ad43-83a48c87ea85",
      "name": "Get Channel Details",
      "type": "n8n-nodes-base.youTube",
      "position": [
        400,
        304
      ],
      "parameters": {
        "part": [
          "snippet",
          "contentDetails"
        ],
        "channelId": "={{ $('Set Audit Options').first().json.channelId }}",
        "operation": "get"
      },
      "typeVersion": 1
    },
    {
      "id": "8bf0e214-0e4c-4c85-baee-46e4550ed027",
      "name": "List Uploads Playlist",
      "type": "n8n-nodes-base.youTube",
      "position": [
        576,
        304
      ],
      "parameters": {
        "part": [
          "snippet",
          "contentDetails"
        ],
        "options": {},
        "resource": "playlistItem",
        "operation": "getAll",
        "returnAll": true,
        "playlistId": "={{ $('Get Channel Details').first().json.contentDetails.relatedPlaylists.uploads }}"
      },
      "typeVersion": 1
    },
    {
      "id": "79db1c78-f8ca-432a-9b9c-a1782b3ce426",
      "name": "Get Video Metadata",
      "type": "n8n-nodes-base.youTube",
      "position": [
        752,
        304
      ],
      "parameters": {
        "part": [
          "snippet",
          "status"
        ],
        "options": {},
        "videoId": "={{ $json.contentDetails.videoId }}",
        "resource": "video",
        "operation": "get"
      },
      "typeVersion": 1
    },
    {
      "id": "489fa3aa-ccdb-44c2-aa0e-6a9fcf57902b",
      "name": "Read Saved Snapshot",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        992,
        304
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 0,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=drivesdk",
          "cachedResultName": "Your Spreadsheet"
        }
      },
      "executeOnce": true,
      "typeVersion": 4.7
    },
    {
      "id": "8f6d8474-f9f9-4379-9dc0-c55c9782b9b8",
      "name": "Diff Metadata Fields",
      "type": "n8n-nodes-base.code",
      "position": [
        1184,
        304
      ],
      "parameters": {
        "jsCode": "const trackedFields = ['title', 'description', 'tags', 'privacyStatus', 'categoryId'];\nconst checkedAt = new Date().toISOString();\n\nconst options = $('Set Audit Options').first().json;\nconst alwaysNotify = options.alwaysNotify === true || options.alwaysNotify === 'true';\n\nconst toStr = (x) => (x === undefined || x === null ? '' : String(x));\n\nconst currentRows = $('Get Video Metadata').all().map((item) => {\n  const v = item.json || {};\n  const s = v.snippet || {};\n  const st = v.status || {};\n  const tags = Array.isArray(s.tags) ? s.tags.slice().sort() : [];\n  return {\n    videoId: toStr(v.id),\n    title: toStr(s.title),\n    description: toStr(s.description),\n    tags: tags.join(' | '),\n    privacyStatus: toStr(st.privacyStatus),\n    categoryId: toStr(s.categoryId)\n  };\n});\n\nconst previousRows = $('Read Saved Snapshot').all().map((i) => i.json || {});\nconst isFirstRun = previousRows.length === 0;\n\nconst prevMap = {};\nfor (const r of previousRows) {\n  const id = toStr(r.videoId);\n  if (id) prevMap[id] = r;\n}\nconst currMap = {};\nfor (const r of currentRows) currMap[r.videoId] = r;\n\nconst changes = [];\nif (!isFirstRun) {\n  for (const cur of currentRows) {\n    const prev = prevMap[cur.videoId];\n    if (!prev) {\n      changes.push({ checkedAt, videoId: cur.videoId, videoTitle: cur.title, field: 'video', oldValue: 'not in snapshot', newValue: 'added to channel' });\n      continue;\n    }\n    for (const f of trackedFields) {\n      const oldV = toStr(prev[f]);\n      const newV = toStr(cur[f]);\n      if (oldV !== newV) {\n        changes.push({ checkedAt, videoId: cur.videoId, videoTitle: cur.title, field: f, oldValue: oldV, newValue: newV });\n      }\n    }\n  }\n  for (const prev of previousRows) {\n    const id = toStr(prev.videoId);\n    if (id && !currMap[id]) {\n      changes.push({ checkedAt, videoId: id, videoTitle: toStr(prev.title), field: 'video', oldValue: 'present in snapshot', newValue: 'removed or hidden' });\n    }\n  }\n}\n\nconst changeCount = changes.length;\nconst videosChanged = [...new Set(changes.map((c) => c.videoId))];\nconst channelTitle = toStr(($('Get Channel Details').first().json.snippet || {}).title) || 'your channel';\n\nlet summaryText;\nif (isFirstRun) {\n  summaryText = '*YouTube metadata audit*\\nBaseline saved for ' + currentRows.length + ' video(s) on ' + channelTitle + '. The first run only records the snapshot, so there is nothing to compare yet.';\n} else if (changeCount === 0) {\n  summaryText = '*YouTube metadata audit*\\nNo metadata changes detected across ' + currentRows.length + ' video(s) on ' + channelTitle + '.';\n} else {\n  const lines = changes.slice(0, 30).map((c) => '- [' + c.videoId + '] ' + c.field + ': ' + JSON.stringify(c.oldValue) + ' to ' + JSON.stringify(c.newValue));\n  let body = lines.join('\\n');\n  if (changeCount > 30) body += '\\n...and ' + (changeCount - 30) + ' more change(s).';\n  summaryText = '*YouTube metadata changes detected*\\n' + channelTitle + ': ' + changeCount + ' change(s) across ' + videosChanged.length + ' video(s).\\n' + body;\n}\n\nconst shouldNotify = changeCount > 0 || alwaysNotify;\n\nreturn [{ json: { isFirstRun, changeCount, hasChanges: changeCount > 0, shouldNotify, videosChanged, channelTitle, checkedAt, summaryText, changes, currentRows } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "ab28f29c-f0d5-4b16-bf55-03ad169ab240",
      "name": "Route by Run Outcome",
      "type": "n8n-nodes-base.switch",
      "position": [
        1376,
        304
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "changes",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "number",
                      "operation": "gt"
                    },
                    "leftValue": "={{ $json.changeCount }}",
                    "rightValue": 0
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "baseline",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "boolean",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.isFirstRun }}",
                    "rightValue": true
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        },
        "looseTypeValidation": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "24cd254a-d617-4acb-8113-6b9598d00090",
      "name": "Split Out Changes",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1664,
        208
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "changes"
      },
      "typeVersion": 1
    },
    {
      "id": "38b324f0-244a-49bd-be35-ab24a9bc98fb",
      "name": "Append to Change Log",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1888,
        208
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Column A",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Column A",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Column B",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Column B",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 0,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=drivesdk",
          "cachedResultName": "Your Spreadsheet"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "9387194a-a020-40d9-aa50-d2c456c6a561",
      "name": "Clear Snapshot Tab",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2176,
        320
      ],
      "parameters": {
        "operation": "clear",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 0,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=drivesdk",
          "cachedResultName": "Your Spreadsheet"
        }
      },
      "executeOnce": true,
      "typeVersion": 4.7
    },
    {
      "id": "dab35e22-c814-442e-95b6-2faf7b8c70f0",
      "name": "Load Current Rows",
      "type": "n8n-nodes-base.code",
      "position": [
        2352,
        320
      ],
      "parameters": {
        "jsCode": "const rows = $('Diff Metadata Fields').first().json.currentRows || [];\nreturn rows.map((r) => ({ json: r }));"
      },
      "typeVersion": 2
    },
    {
      "id": "b8bb264f-a0f3-446d-9d4d-806e49c392fc",
      "name": "Refresh Snapshot Rows",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2544,
        320
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Column A",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Column A",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Column B",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Column B",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 0,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=drivesdk",
          "cachedResultName": "Your Spreadsheet"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "5be0f1c6-33af-4f00-afc1-8ede2743a6b1",
      "name": "Should Send Alert",
      "type": "n8n-nodes-base.if",
      "position": [
        1664,
        480
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.shouldNotify }}",
              "rightValue": true
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "5ce20482-0b50-45de-8263-600e4eeb9509",
      "name": "Post Summary to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1888,
        464
      ],
      "parameters": {
        "text": "={{ $('Diff Metadata Fields').first().json.summaryText }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SLACK_CHANNEL_ID",
          "cachedResultName": "your-channel"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "36c79282-b090-4017-9c33-78531c5f6998",
      "name": "Sticky Note 35e885bb",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -528,
        -96
      ],
      "parameters": {
        "width": 460,
        "height": 820,
        "content": "## Audit YouTube video metadata changes against a saved snapshot\n\n### How it works\n1. A daily schedule reads your channel, lists every upload, and pulls each video's current title, description, tags, privacy status, and category.\n2. Each field is compared against the snapshot saved on the previous run, and every difference is collected as an old value to new value pair.\n3. Changed fields are appended to a Change Log tab, then the current values overwrite the Snapshot tab so the next run compares against today.\n4. Slack receives a summary of what changed, and stays quiet when nothing changed unless you turn on alwaysNotify.\n\n### Setup steps\n- [ ] Connect a YouTube (Google) OAuth2 credential on the three YouTube nodes.\n- [ ] Enter your channel ID in `Set Audit Options`.\n- [ ] Connect your Google Sheets credential and select your audit spreadsheet in all four Google Sheets nodes.\n- [ ] Add a tab named `Snapshot` and a tab named `Change Log` in that spreadsheet.\n- [ ] Connect your Slack credential and pick the alert channel in `Post Summary to Slack`.\n\n### Customization\nChange the schedule interval, add or remove tracked fields in `Diff Metadata Fields`, or set `alwaysNotify` to true to also post when nothing changed."
      },
      "typeVersion": 1
    },
    {
      "id": "c0c4f82b-9838-4641-af37-7bb45da737cc",
      "name": "Sticky Note cfa0e5c1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 356,
        "height": 344,
        "content": "## Set up the daily run\n\nEnter your channel ID and the alert toggle here. This is a read-only audit and never writes to YouTube."
      },
      "typeVersion": 1
    },
    {
      "id": "e75fa883-03bd-4683-8613-6319d970e0d9",
      "name": "Sticky Note 71b979d3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        352,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 344,
        "content": "## Enumerate the channel library\n\nResolves the uploads playlist and reads current metadata for every video. Connect your YouTube (Google) OAuth2 credential on these three nodes."
      },
      "typeVersion": 1
    },
    {
      "id": "a2c102e7-c7d4-46cd-8eda-6319bbd7241a",
      "name": "Sticky Note 5bfa387b",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        944,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 628,
        "height": 344,
        "content": "## Diff against the snapshot\n\nReads the last snapshot and compares each tracked field. The first run just records a baseline."
      },
      "typeVersion": 1
    },
    {
      "id": "0bfc2894-b13a-40f2-a4f2-9138c7f7cc0d",
      "name": "Sticky Note 0800ad36",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1616,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 612,
        "content": "## Log changes and alert\n\nAppends each changed field to the Change Log, then posts a Slack summary when there is something to report."
      },
      "typeVersion": 1
    },
    {
      "id": "302e0045-246b-4c9b-8762-abdb3b619711",
      "name": "Sticky Note 105d0824",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2128,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 344,
        "content": "## Refresh the snapshot\n\nOverwrites the Snapshot tab with the current values so the next run compares against today. Runs after the Change Log write."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "connections": {
    "Run Daily Audit": {
      "main": [
        [
          {
            "node": "Set Audit Options",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Current Rows": {
      "main": [
        [
          {
            "node": "Refresh Snapshot Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Audit Options": {
      "main": [
        [
          {
            "node": "Get Channel Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Should Send Alert": {
      "main": [
        [
          {
            "node": "Post Summary to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Changes": {
      "main": [
        [
          {
            "node": "Append to Change Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clear Snapshot Tab": {
      "main": [
        [
          {
            "node": "Load Current Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Video Metadata": {
      "main": [
        [
          {
            "node": "Read Saved Snapshot",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Channel Details": {
      "main": [
        [
          {
            "node": "List Uploads Playlist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Saved Snapshot": {
      "main": [
        [
          {
            "node": "Diff Metadata Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append to Change Log": {
      "main": [
        [
          {
            "node": "Clear Snapshot Tab",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Diff Metadata Fields": {
      "main": [
        [
          {
            "node": "Route by Run Outcome",
            "type": "main",
            "index": 0
          },
          {
            "node": "Should Send Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Run Outcome": {
      "main": [
        [
          {
            "node": "Split Out Changes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Clear Snapshot Tab",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List Uploads Playlist": {
      "main": [
        [
          {
            "node": "Get Video Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

This workflow runs daily to fetch all videos from a YouTube channel, compare key metadata fields against a prior snapshot stored in Google Sheets, log any differences, and optionally post a summary to Slack. Runs every day at 08:00 on a schedule. Reads your YouTube channel’s…

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

More Social Media workflows → · Browse all categories →

Related workflows

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

Social Media

This enterprise-grade n8n workflow automates the Instagram complaint handling process — from detection to resolution — using Claude AI, dynamic ticket assignment, and SLA enforcement. It converts cust

HTTP Request, Google Sheets, Slack
Social Media

Multi YT To TT. Uses googleSheets, httpRequest, youTube. Scheduled trigger; 30 nodes.

Google Sheets, HTTP Request, YouTube
Social Media

This automation runs daily to fetch the latest videos on Youtube from leading AI automators, such as: Nate Herk Nick Saraev Jack Roberts Cole Medin Nick Puru Ed Hill Jason Cooperson Manthan Patel Nick

Google Sheets, YouTube
Social Media

This enterprise-grade n8n workflow automates influencer contract compliance for Instagram campaigns — from deadline tracking to breach detection — using Claude AI, Instagram API, and smart reminders.

Google Sheets, Slack, HTTP Request
Social Media

Video Creation from Google Sheets and Upload to YouTube with VideoApiHub

Google Sheets, N8N Nodes Video Api Hub, YouTube