AutomationFlowsAI & RAG › YouTube Video Metadata Generator

YouTube Video Metadata Generator

Original n8n title: Generate Youtube Video Metadata (timestamps, Tags, Description, ...)

ByNasser @nasser on n8n.io

Content Creators Youtube Automation Marketing Team

Event trigger★★★★☆ complexityAI-powered22 nodesLm Chat Mistral CloudOutput Parser StructuredHTTP RequestChain LlmRss Feed Read TriggerYouTube
AI & RAG Trigger: Event Nodes: 22 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Chainllm → HTTP Request 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
{
  "id": "",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "YTB Metadata Generator",
  "tags": [],
  "nodes": [
    {
      "id": "",
      "name": "Mistral Cloud Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatMistralCloud",
      "position": [
        700,
        -40
      ],
      "parameters": {
        "model": "mistral-large-latest",
        "options": {}
      },
      "credentials": {
        "mistralCloudApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "No Operation, do nothing",
      "type": "n8n-nodes-base.noOp",
      "position": [
        -440,
        120
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        860,
        -40
      ],
      "parameters": {
        "jsonSchemaExample": "[\n  {\n    \"name\": \"preview\",\n    \"type\": \"string\",\n    \"description\": \"Short preview (100\u2013200 characters, no hashtags)\"\n  },\n  {\n    \"name\": \"timestamps\",\n    \"type\": \"string\",\n    \"description\": \"YouTube-style timestamps, each on a new line, starts with 00:00\"\n  },\n  {\n    \"name\": \"tags\",\n    \"type\": \"string\",\n    \"description\": \"Comma-separated keywords (no hashtags)\"\n  }\n]\n"
      },
      "typeVersion": 1.2
    },
    {
      "id": "",
      "name": "No Operation, do nothing1",
      "type": "n8n-nodes-base.noOp",
      "position": [
        700,
        100
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "Check IF Finished",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -160,
        -80
      ],
      "parameters": {
        "url": "https://api.apify.com/v2/acts/streamers~youtube-scraper/runs/last?token=YOUR_TOKEN_HERE",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "",
      "name": "Get DataSet",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        240,
        -100
      ],
      "parameters": {
        "url": "https://api.apify.com/v2/acts/streamers~youtube-scraper/runs/last/dataset/items?token=YOUR_TOKEN_HERE",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "",
      "name": "Scrape Video",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -440,
        -80
      ],
      "parameters": {
        "url": "https://api.apify.com/v2/acts/streamers~youtube-scraper/runs?token=YOUR_TOKEN_HERE",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n    \"downloadSubtitles\": true,\n    \"preferAutoGeneratedSubtitles\": false,\n    \"startUrls\": [\n        {\n            \"url\": \"{{ $('Trigger New Video Posted').item.json.link }}\",\n            \"method\": \"GET\"\n        }\n    ],\n    \"subtitlesLanguage\": \"en\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "",
      "name": "If Finished",
      "type": "n8n-nodes-base.if",
      "position": [
        20,
        -80
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data.status }}",
              "rightValue": "SUCCEEDED"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        180,
        120
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "",
      "name": "If Recent",
      "type": "n8n-nodes-base.if",
      "position": [
        -660,
        20
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ new Date($json[\"pubDate\"]).getTime() }}",
              "rightValue": "={{ new Date().getTime() - 10 * 60 * 1000 }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "",
      "name": "If Not Generated",
      "type": "n8n-nodes-base.if",
      "position": [
        460,
        -100
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "",
              "operator": {
                "type": "string",
                "operation": "notContains"
              },
              "leftValue": "={{ $json.text }}",
              "rightValue": "00:00"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "",
      "name": "Generate Description",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        700,
        -200
      ],
      "parameters": {
        "text": "You are given the full script of a YouTube video. Your task is to extract metadata in a structured format to be used in a YouTube upload.",
        "messages": {
          "messageValues": [
            {
              "type": "HumanMessagePromptTemplate",
              "message": "=Script Input:\n{{ $json.subtitles[0].srt }}\n\nFinal Output Format:\n\nPreview:\n[short preview description]\n\nTimestamps:\n00:00 [Title]\n00:30 [Title]\n...\n\nTags:\ntag1,tag2,tag3,...\n\nFollow these instructions strictly:\n\n1. Short Preview Description\nWrite a short preview (100\u2013200 characters max) of the video.\nUse engaging language, and include relevant keywords from the topic of the video.\nThis should be a single sentence or two, no hashtags, no line breaks.\n\n2. Timestamps\nFormat: MM:SS Timestamp Title (each on a new line)\nMust start with 00:00\nEach timestamp section must be at least 30 seconds apart\nOnly 3 to 7 total timestamps\nEach timestamp title should be maximum 3 words, summarize the section clearly\nExample format:\n\"00:00 Intro  \n00:47 Key Concept  \n02:12 Real Example\"\n\n3. Tags\nProvide 5 to 10 relevant SEO-friendly keywords separated by commas\nNo hashtags\nFormat like this:\n\"automation,n8n,ai workflows,youtube growth,openai\"\n"
            }
          ]
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.5
    },
    {
      "id": "",
      "name": "Format ",
      "type": "n8n-nodes-base.code",
      "position": [
        1100,
        -200
      ],
      "parameters": {
        "jsCode": "// Assuming input data contains preview, timestamps, tags\nconst preview = $input.first().json.output[0].description;\nconst timestamps = $input.first().json.output[1].description;\n\n// Hardcoded links section (edit as needed)\nconst links = `\nLinks:\n- Website: https://example.com\n- Twitter: https://twitter.com/example\n- GitHub: https://github.com/example\n`;\n\n// Format description string\nconst description = preview + \"\\n\" +links + \"\\n\" +timestamps \n\n// Return the formatted description\nreturn [\n  {\n    json: {\n      description: description.trim(),\n    },\n  },\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -940,
        -100
      ],
      "parameters": {
        "color": 7,
        "width": 420,
        "height": 340,
        "content": "## 1- Input\nEnter the ID of the YTB channel to trigger the workflow when a new video is posted"
      },
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -500,
        -180
      ],
      "parameters": {
        "color": 7,
        "height": 480,
        "content": "## 2- Create DataSet\nApify scrape the last YTB video of the channel"
      },
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        420,
        -280
      ],
      "parameters": {
        "color": 7,
        "width": 600,
        "height": 580,
        "content": "## 4- Check and Generate Metadata\nVerify if Metadata are not already generated and generate them with LLM\n"
      },
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        -280
      ],
      "parameters": {
        "color": 7,
        "width": 460,
        "height": 240,
        "content": "## 5- Output\nFormat all the data created and update YTB Video"
      },
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -240,
        -180
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 500,
        "content": "## 3- Wait for Completion & Get DataSet\nWait until the dataset is completed in Apify and get it"
      },
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -940,
        -800
      ],
      "parameters": {
        "color": 7,
        "width": 780,
        "height": 240,
        "content": "## How it works?\n**1 -** Enter the ID of the YTB channel to trigger the workflow when a new video is posted\n**2 -** Apify scrape the last YTB video of the channel\n**3 -** Wait until the dataset is completed in Apify and get it\n**4 -** Verify if Metadata are not already generated and generate them with LLM\n**5 -** Format all the data created and update YTB Video\n\n**\ud83d\udcfa Youtube Video Tutorial : https://youtu.be/HaQPAa6l5bU**\n**\ud83d\udee0\ufe0f Need Help with Your Workflows ? https://tally.so/r/wayeqB**\n**\ud83d\udc68\u200d\ud83d\udcbb\u00a0More Workflows : https://n8n.io/creators/nasser/**"
      },
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -940,
        -540
      ],
      "parameters": {
        "color": 7,
        "width": 780,
        "height": 340,
        "content": "## SETUP\n\n**Setup Input YTB Chanel :** Go to the channel's page on YouTube, and look at the URL of the page. The channel ID is the value that comes after channel/ in the URL. Add it after \"?channel_id=\" You can also use free tools available to retrieve channel ID.\n\n**Setup Output YTB Video Update :** Connect your YTB account to your n8n instance thanks to the Google Cloud Console. You can find tutorials by typing \"youtube api Oauth\" on Google.\n\n**APIs :** For the following third-party integrations, replace [YOUR_API_TOKEN] with your API Token or connect your account via Client ID / Secret to your n8n instance : \n- Apify : https://docs.apify.com/api/v2/getting-started\n- Youtube : https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.youtube/?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=n8n-nodes-base.youTube#templates-and-examples"
      },
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "Trigger New Video Posted",
      "type": "n8n-nodes-base.rssFeedReadTrigger",
      "position": [
        -880,
        20
      ],
      "parameters": {
        "feedUrl": "https://www.youtube.com/feeds/videos.xml?channel_id=[YOUR_CHANNEL_ID]",
        "pollTimes": {
          "item": [
            {
              "mode": "everyX",
              "unit": "minutes",
              "value": 10
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "",
      "name": "Update YTB Video",
      "type": "n8n-nodes-base.youTube",
      "position": [
        1280,
        -200
      ],
      "parameters": {
        "title": "={{ $('Get DataSet').item.json.title }}",
        "videoId": "={{ $('Get DataSet').item.json.id }}",
        "resource": "video",
        "operation": "update",
        "regionCode": "US",
        "updateFields": {
          "tags": "={{ $('Generate Description').item.json.output[2].description }}",
          "description": "={{ $json.description }}"
        }
      },
      "credentials": {
        "youTubeOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "",
  "connections": {
    "Wait": {
      "main": [
        [
          {
            "node": "Check IF Finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format ": {
      "main": [
        [
          {
            "node": "Update YTB Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Recent": {
      "main": [
        [
          {
            "node": "Scrape Video",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Operation, do nothing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get DataSet": {
      "main": [
        [
          {
            "node": "If Not Generated",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Finished": {
      "main": [
        [
          {
            "node": "Get DataSet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Video": {
      "main": [
        [
          {
            "node": "Check IF Finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Not Generated": {
      "main": [
        [
          {
            "node": "Generate Description",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Operation, do nothing1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check IF Finished": {
      "main": [
        [
          {
            "node": "If Finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Description": {
      "main": [
        [
          {
            "node": "Format ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mistral Cloud Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Description",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Generate Description",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Trigger New Video Posted": {
      "main": [
        [
          {
            "node": "If Recent",
            "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

Content Creators Youtube Automation Marketing Team

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

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This n8n workflow automates the entire process of creating and publishing AI-generated videos, triggered by a simple message from a Telegram bot (YTAdmin). It transforms a text prompt into a structure

Lm Chat Mistral Cloud, HTTP Request, YouTube +5
AI & RAG

My workflow 53. Uses formTrigger, httpRequest, lmChatOpenAi, form. Event-driven trigger; 74 nodes.

Form Trigger, HTTP Request, OpenAI Chat +15
AI & RAG

Episode 23: UGC with nanobanana. Uses lmChatOpenAi, lmChatOllama, lmChatDeepSeek, lmChatOpenRouter. Event-driven trigger; 74 nodes.

OpenAI Chat, Ollama Chat, Lm Chat Deep Seek +12
AI & RAG

It transforms a single text prompt into a fully scripted, visually rich video with AI-generated images and voiceovers, then distributes it across multiple social media platforms. Content Creators & Yo

Google Gemini Chat, Agent, Chain Llm +5
AI & RAG

Content Creators Youtube Automation Marketing Team

Output Parser Structured, Chain Llm, HTTP Request +2