{
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true,
    "templateCredsSetupCompleted": false
  },
  "name": "Rotate YouTube channel banners on a schedule using Google Sheets and Drive",
  "tags": [],
  "nodes": [
    {
      "id": "a5d7e322-482c-44cf-baa1-f1d39bb22c96",
      "name": "Run Daily at 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        560,
        352
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "44bfaa00-b9b6-41d0-ab38-672297fe9873",
      "name": "Set Banner Config",
      "type": "n8n-nodes-base.set",
      "position": [
        768,
        352
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "channel-id",
              "name": "channelId",
              "type": "string",
              "value": "YOUR_CHANNEL_ID"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "960e235f-b9ad-4816-a0dc-74d28d0d5865",
      "name": "Read Banner Schedule",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1008,
        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": "1758639e-5c3f-432c-aee7-8cea25e4f95b",
      "name": "Select Due Banners",
      "type": "n8n-nodes-base.code",
      "position": [
        1232,
        352
      ],
      "parameters": {
        "jsCode": "function toIsoDate(value) {\n  const s = String(value ?? '').trim();\n  if (!s) return null;\n  if (/^\\d+(\\.\\d+)?$/.test(s)) {\n    // Google Sheets date serial (days since 1899-12-30)\n    const d = DateTime.fromISO('1899-12-30').plus({ days: Math.floor(Number(s)) });\n    return d.isValid ? d.toFormat('yyyy-MM-dd') : null;\n  }\n  const d = DateTime.fromFormat(s, 'yyyy-MM-dd');\n  return d.isValid ? d.toFormat('yyyy-MM-dd') : null;\n}\n\nconst today = $today.toFormat('yyyy-MM-dd');\nconst due = [];\nfor (const item of $input.all()) {\n  const row = item.json;\n  const swapDate = toIsoDate(row['Swap Date']);\n  const fileId = String(row['Banner Drive File ID'] ?? '').trim();\n  const status = String(row['Status'] ?? '').trim().toLowerCase();\n  if (!swapDate || !fileId) continue;\n  if (status === 'applied') continue;\n  if (swapDate > today) continue;\n  item.json.swapDateIso = swapDate;\n  due.push(item);\n}\ndue.sort((a, b) => a.json.swapDateIso.localeCompare(b.json.swapDateIso));\nreturn due;"
      },
      "typeVersion": 2
    },
    {
      "id": "2d51bc73-a256-459d-9afa-50cbf7eed480",
      "name": "Download Banner Image",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1536,
        352
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json['Banner Drive File ID'] }}"
        },
        "options": {
          "binaryPropertyName": "data"
        },
        "operation": "download"
      },
      "typeVersion": 3
    },
    {
      "id": "6f1fe997-7a89-4b61-b4ee-53cb54d4115a",
      "name": "Upload Banner Image",
      "type": "n8n-nodes-base.youTube",
      "position": [
        1792,
        352
      ],
      "parameters": {
        "channelId": "={{ $('Set Banner Config').first().json.channelId }}",
        "operation": "uploadBanner"
      },
      "typeVersion": 1
    },
    {
      "id": "87718eaf-4c32-4a4d-ab78-05210b8d007e",
      "name": "Set New Banner as Active",
      "type": "n8n-nodes-base.youTube",
      "position": [
        2336,
        352
      ],
      "parameters": {
        "channelId": "={{ $('Set Banner Config').first().json.channelId }}",
        "operation": "update",
        "updateFields": {
          "brandingSettingsUi": {
            "imageSettingsValues": {
              "image": {
                "bannerExternalUrl": "={{ $('Upload Banner Image').item.json.url }}"
              }
            },
            "channelSettingsValues": {
              "channel": {
                "country": "={{ $json.brandingSettings?.channel?.country ?? '' }}",
                "keywords": "={{ $json.brandingSettings?.channel?.keywords ?? '' }}",
                "description": "={{ $json.brandingSettings?.channel?.description ?? '' }}",
                "unsubscribedTrailer": "={{ $json.brandingSettings?.channel?.unsubscribedTrailer ?? '' }}"
              }
            }
          }
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4c78ac56-08da-41e9-8287-a9e780f8aebc",
      "name": "Mark Row as Applied",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2560,
        352
      ],
      "parameters": {
        "columns": {
          "value": {
            "Status": "Applied",
            "row_number": "={{ $('Select Due Banners').item.json.row_number }}"
          },
          "schema": [
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "row_number"
          ]
        },
        "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": "2c88d015-34cd-4071-b0df-36a649e6cfc5",
      "name": "Log the Banner Swap",
      "type": "n8n-nodes-base.set",
      "position": [
        2784,
        352
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "swapped-at",
              "name": "swappedAt",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            },
            {
              "id": "channel-id",
              "name": "channelId",
              "type": "string",
              "value": "={{ $('Set Banner Config').first().json.channelId }}"
            },
            {
              "id": "banner-label",
              "name": "bannerLabel",
              "type": "string",
              "value": "={{ $('Select Due Banners').item.json.Label }}"
            },
            {
              "id": "swap-date",
              "name": "swapDate",
              "type": "string",
              "value": "={{ $('Select Due Banners').item.json.swapDateIso }}"
            },
            {
              "id": "banner-file-id",
              "name": "bannerFileId",
              "type": "string",
              "value": "={{ $('Select Due Banners').item.json['Banner Drive File ID'] }}"
            },
            {
              "id": "banner-url",
              "name": "bannerUrl",
              "type": "string",
              "value": "={{ $('Upload Banner Image').item.json.url }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "48f6ff60-ad27-444b-b125-a96e0301d5c8",
      "name": "Sticky Note f7779d20",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -128,
        -80
      ],
      "parameters": {
        "width": 560,
        "height": 928,
        "content": "## Rotate YouTube channel banners on a schedule\n\n### How it works\n\n1. A Schedule Trigger runs the workflow every morning at 8am.\n2. Edit Fields holds your channel ID so you set it once.\n3. Google Sheets reads the banner schedule, one row per planned swap: a swap date, a Drive file ID, an optional label, and a Status column the workflow maintains.\n4. A Code node keeps rows due today or overdue that are not yet Applied, sorted oldest first, so a missed day catches up on the next run and the newest banner ends up live. Swap dates work as plain yyyy-MM-dd text or as real date cells.\n5. Google Drive downloads the banner art and YouTube uploads it with Channel: uploadBanner.\n6. The workflow reads your current channel branding, then Channel: update activates the new banner while re-sending your description, keywords, country, and trailer, because the YouTube API clears any branding field the update leaves out.\n7. Google Sheets marks the row Applied and a final step records what changed.\n\n### Setup steps\n\n- [ ] Connect a YouTube (Google) OAuth2 credential on the three YouTube nodes.\n- [ ] Connect your Google Sheets and Google Drive credentials.\n- [ ] In Set Banner Config, set channelId to your YouTube channel ID.\n- [ ] Point both Google Sheets nodes at your schedule spreadsheet and tab.\n- [ ] Add sheet columns Swap Date (yyyy-MM-dd), Banner Drive File ID, Label, and Status. Leave Status empty, the workflow fills it.\n\nBanner art: 2048x1152 px and under 6 MB. Adjust the run time in the Schedule Trigger."
      },
      "typeVersion": 1
    },
    {
      "id": "8400d3ed-7de4-4291-a709-6123465f1702",
      "name": "Sticky Note e998aea5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        208
      ],
      "parameters": {
        "color": 7,
        "width": 896,
        "height": 320,
        "content": "## Check the banner schedule\n\nRead the schedule and keep the rows due today or overdue that have not been applied yet, oldest first."
      },
      "typeVersion": 1
    },
    {
      "id": "daad78ca-ec09-4983-9624-c97eabb199ea",
      "name": "Sticky Note 190b6aa2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1456,
        208
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 320,
        "content": "## Fetch and upload\n\nDownload the banner art from Drive and upload it to the channel."
      },
      "typeVersion": 1
    },
    {
      "id": "374f2eda-a285-4228-9488-d15d070106ec",
      "name": "Sticky Note 5fbbdf1f",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2048,
        208
      ],
      "parameters": {
        "color": 7,
        "width": 928,
        "height": 320,
        "content": "## Activate, mark, log\n\nRead the current branding, set the new banner live without clearing it, mark the row Applied, and record the swap."
      },
      "typeVersion": 1
    },
    {
      "id": "a8656054-5e39-4573-8e0a-ff756a345dad",
      "name": "Get Current Branding",
      "type": "n8n-nodes-base.youTube",
      "position": [
        2112,
        352
      ],
      "parameters": {
        "part": [
          "brandingSettings"
        ],
        "channelId": "={{ $('Set Banner Config').first().json.channelId }}",
        "operation": "get"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "connections": {
    "Run Daily at 8am": {
      "main": [
        [
          {
            "node": "Set Banner Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Banner Config": {
      "main": [
        [
          {
            "node": "Read Banner Schedule",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Due Banners": {
      "main": [
        [
          {
            "node": "Download Banner Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Row as Applied": {
      "main": [
        [
          {
            "node": "Log the Banner Swap",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Banner Image": {
      "main": [
        [
          {
            "node": "Get Current Branding",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Current Branding": {
      "main": [
        [
          {
            "node": "Set New Banner as Active",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Banner Schedule": {
      "main": [
        [
          {
            "node": "Select Due Banners",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Banner Image": {
      "main": [
        [
          {
            "node": "Upload Banner Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set New Banner as Active": {
      "main": [
        [
          {
            "node": "Mark Row as Applied",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}