AutomationFlowsSocial Media › Unpublish Expired Youtube Videos Using Google Sheets and Slack

Unpublish Expired Youtube Videos Using Google Sheets and Slack

ByKevin Yu @exekyute on n8n.io

This workflow runs daily and checks a Google Sheets embargo schedule for YouTube videos whose unpublish date has passed, then updates their privacy status on YouTube and logs the result back to the sheet while sending a recap (or “nothing due”) message to Slack. Runs every day…

Cron / scheduled trigger★★★★☆ complexity16 nodesGoogle SheetsYouTubeSlack
Social Media Trigger: Cron / scheduled Nodes: 16 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #17091 — 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": "Auto un-publish expired YouTube videos on a schedule",
  "tags": [],
  "nodes": [
    {
      "id": "ba669071-0fd8-4c01-b350-781562df437d",
      "name": "Run Daily at 6am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        560,
        352
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 6
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "6ae40f08-db74-452a-8879-84ea8e810a0d",
      "name": "Read Embargo Schedule",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        736,
        352
      ],
      "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"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "7963c245-b0d2-4e0a-bccf-036876fa4052",
      "name": "Select Due Videos",
      "type": "n8n-nodes-base.code",
      "position": [
        912,
        352
      ],
      "parameters": {
        "jsCode": "// === CONFIG ===\n// Set your channel timezone (IANA name, e.g. America/Halifax, Europe/London).\nconst TIMEZONE = 'America/Halifax';\n// Status to apply when a row leaves \"Target Status\" blank.\nconst DEFAULT_TARGET = 'private';\n\nconst today = DateTime.now().setZone(TIMEZONE).startOf('day');\nconst due = [];\n\nfor (const item of $input.all()) {\n  const row = item.json;\n  const videoId = (row.videoId || '').toString().trim();\n  const rawDate = (row['Unpublish Date'] || '').toString().trim();\n  const status = (row.Status || '').toString().trim().toLowerCase();\n\n  if (!videoId || !rawDate) continue;\n  if (status === 'expired') continue;\n\n  let unpublish = DateTime.fromISO(rawDate, { zone: TIMEZONE });\n  if (!unpublish.isValid) {\n    unpublish = DateTime.fromFormat(rawDate, 'yyyy-MM-dd', { zone: TIMEZONE });\n  }\n  if (!unpublish.isValid) continue;\n\n  if (unpublish.startOf('day') <= today) {\n    let target = (row['Target Status'] || DEFAULT_TARGET).toString().trim().toLowerCase();\n    if (target !== 'private' && target !== 'unlisted') target = DEFAULT_TARGET;\n    due.push({\n      videoId,\n      targetStatus: target,\n      unpublishDate: rawDate,\n      title: (row.Title || '').toString()\n    });\n  }\n}\n\nreturn [{ json: { dueCount: due.length, due } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "744be8c2-82ee-4379-8c45-23784b7dd7e4",
      "name": "If Any Videos Due",
      "type": "n8n-nodes-base.if",
      "position": [
        1152,
        352
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.dueCount }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "3c9601a6-9755-483d-8745-9592732a6eb9",
      "name": "Split Due Videos",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        1328,
        256
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "due"
      },
      "typeVersion": 1
    },
    {
      "id": "cc099a11-f8f0-4876-82a8-f93b70c3e695",
      "name": "Get Current Video Details",
      "type": "n8n-nodes-base.youTube",
      "position": [
        1616,
        256
      ],
      "parameters": {
        "part": [
          "snippet",
          "status"
        ],
        "options": {},
        "videoId": "={{ $json.videoId }}",
        "resource": "video",
        "operation": "get"
      },
      "typeVersion": 1
    },
    {
      "id": "4d533eec-3709-4560-a6ed-d164d9d3e725",
      "name": "If Already at Target",
      "type": "n8n-nodes-base.if",
      "position": [
        1776,
        256
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status.privacyStatus }}",
              "rightValue": "={{ $(\"Split Due Videos\").item.json.targetStatus }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "b8097d75-bc7d-4efd-a773-2e4710f4ef17",
      "name": "Mark Row as Expired",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2192,
        240
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Expired",
            "videoId": "={{ $(\"Split Due Videos\").item.json.videoId }}",
            "Unpublished At": "={{ $now.toISO() }}"
          },
          "schema": [
            {
              "id": "videoId",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "videoId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "Unpublished At",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Unpublished At",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "videoId"
          ]
        },
        "options": {},
        "operation": "update",
        "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": "7382aa66-57a2-4b51-a88f-c6f17254c2ba",
      "name": "Set Video to Embargo Status",
      "type": "n8n-nodes-base.youTube",
      "position": [
        1936,
        384
      ],
      "parameters": {
        "title": "={{ $json.snippet.title }}",
        "videoId": "={{ $json.id }}",
        "resource": "video",
        "operation": "update",
        "categoryId": "={{ $json.snippet.categoryId }}",
        "updateFields": {
          "privacyStatus": "={{ $(\"Split Due Videos\").item.json.targetStatus }}"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7a4f4d94-4b94-435b-9c15-730966db6d8b",
      "name": "Post Nothing Due Notice",
      "type": "n8n-nodes-base.slack",
      "position": [
        1328,
        448
      ],
      "parameters": {
        "text": "No YouTube videos were due for embargo today. Nothing to un-publish.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SLACK_CHANNEL_ID",
          "cachedResultName": "your-channel"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "28e5a9fb-636d-4532-b86c-2493f3e68ca3",
      "name": "Post Embargo Recap",
      "type": "n8n-nodes-base.slack",
      "position": [
        2368,
        240
      ],
      "parameters": {
        "text": "=*YouTube embargo run complete*\n\nThe following videos are now at their embargo status:\n{{ $(\"Get Current Video Details\").all().map((v, i) => \"\u2022 \" + (v.json.snippet.title || v.json.id) + \" (\" + v.json.id + \") is now \" + $(\"Split Due Videos\").all()[i].json.targetStatus).join(\"\\n\") }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SLACK_CHANNEL_ID",
          "cachedResultName": "your-channel"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "executeOnce": true,
      "typeVersion": 2.5
    },
    {
      "id": "64347221-ddd6-4045-a07e-b060e4038848",
      "name": "Sticky Note 0276c973",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        -32
      ],
      "parameters": {
        "width": 556,
        "height": 776,
        "content": "## Auto un-publish expired YouTube videos on a schedule\n\n### How it works\n1. A schedule trigger runs the workflow once a day.\n2. Google Sheets reads a control sheet of videos, each with an unpublish date and an optional target status.\n3. A Code step selects rows whose unpublish date has passed and that are not yet marked `Expired`, using a fixed timezone.\n4. For each due video, YouTube fetches the current details and any video already at its target status is skipped.\n5. YouTube sets the remaining videos to `private` or `unlisted`, preserving the existing title and category.\n6. Google Sheets marks each processed row `Expired` and Slack posts a recap, or a \"nothing due\" note.\n\n### Setup steps\n- [ ] Connect a YouTube (Google) OAuth2 credential on both YouTube nodes.\n- [ ] Connect a Google Sheets credential and pick your embargo control sheet.\n- [ ] Give the sheet the columns `videoId`, `Unpublish Date`, `Target Status`, and `Status`.\n- [ ] Connect a Slack credential and choose the channel for the recap.\n- [ ] Set the `TIMEZONE` constant at the top of the `Select Due Videos` node.\n\n### Customization\nRun the schedule more than once a day, switch the default target from `private` to `unlisted`, or keep the `Unpublished At` column to timestamp every change."
      },
      "typeVersion": 1
    },
    {
      "id": "fc23712d-b2f6-4f06-97f8-e91c9536e1a3",
      "name": "Sticky Note 359ba2dc",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        256
      ],
      "parameters": {
        "color": 7,
        "width": 548,
        "height": 264,
        "content": "## Read and select due rows"
      },
      "typeVersion": 1
    },
    {
      "id": "1904ce18-362e-484e-9af0-d854134e4875",
      "name": "Sticky Note 0b2f745e",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 412,
        "height": 480,
        "content": "## Branch on due count"
      },
      "typeVersion": 1
    },
    {
      "id": "1556509d-c78c-42e0-8e64-14d71548c96c",
      "name": "Sticky Note 803ce945",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1568,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 520,
        "height": 412,
        "content": "## Confirm and un-publish"
      },
      "typeVersion": 1
    },
    {
      "id": "03fa87ed-c2f2-4106-b752-23f664afc43c",
      "name": "Sticky Note e880fa60",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2144,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 412,
        "height": 248,
        "content": "## Log and notify"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "connections": {
    "Run Daily at 6am": {
      "main": [
        [
          {
            "node": "Read Embargo Schedule",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Due Videos": {
      "main": [
        [
          {
            "node": "Get Current Video Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Any Videos Due": {
      "main": [
        [
          {
            "node": "Split Due Videos",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post Nothing Due Notice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Due Videos": {
      "main": [
        [
          {
            "node": "If Any Videos Due",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Row as Expired": {
      "main": [
        [
          {
            "node": "Post Embargo Recap",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Already at Target": {
      "main": [
        [
          {
            "node": "Mark Row as Expired",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set Video to Embargo Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Embargo Schedule": {
      "main": [
        [
          {
            "node": "Select Due Videos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Current Video Details": {
      "main": [
        [
          {
            "node": "If Already at Target",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Video to Embargo Status": {
      "main": [
        [
          {
            "node": "Mark Row as Expired",
            "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 and checks a Google Sheets embargo schedule for YouTube videos whose unpublish date has passed, then updates their privacy status on YouTube and logs the result back to the sheet while sending a recap (or “nothing due”) message to Slack. Runs every day…

Source: https://n8n.io/workflows/17091/ — 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 n8n workflow automates the process of promoting your latest YouTube videos on X (formerly Twitter). It ensures that posts are engaging, concise, and follow platform-specific requirements. Additio

Twitter, YouTube, Tool Calculator +6
Social Media

This template is ideal for sales teams, recruiters, business development professionals, and relationship managers who need to monitor changes in their network's LinkedIn profiles. Perfect for agencies

Google Sheets, HTTP Request, Slack
Social Media

video-publisher. Uses googleSheets, youTube, httpRequest. Scheduled trigger; 6 nodes.

Google Sheets, YouTube, HTTP Request
Social Media

This workflow automates the post-publish process for YouTube videos, combining advanced SEO optimization, cross-platform promotion, and analytics reporting. It is designed for creators, marketers, and

YouTube, HTTP Request, LinkedIn +6
Social Media

This workflow runs on a schedule, pulls the next publish-ready video from Airtable, uses OpenAI, Google Gemini, and CloudConvert to generate a transcript, title, description, tags, and hashtags, then

Airtable, Facebook Graph Api, OpenAI +5