Skip to content

Instantly share code, notes, and snippets.

@joshdvir
Created February 2, 2026 18:06
Show Gist options
  • Select an option

  • Save joshdvir/3436fb6a276d801c4fc68915657cbf92 to your computer and use it in GitHub Desktop.

Select an option

Save joshdvir/3436fb6a276d801c4fc68915657cbf92 to your computer and use it in GitHub Desktop.
Download and organize high-quality YouTube videos
{
"id": "hhiseK9fck5oYA2w",
"meta": {
"instanceId": "06dd8d98e2c191f45688601e8c95c27ad0a7ce28525ba5119fe9a1164cd17fe5"
},
"name": "Download and organize high-quality YouTube videos",
"tags": [],
"nodes": [
{
"id": "940b8146-9e24-491d-afa3-cc8947287d70",
"name": "When clicking ‘Execute workflow’",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-3536,
320
],
"parameters": {},
"typeVersion": 1
},
{
"id": "cf2e59f7-1a5d-4042-a525-87a87a309ab9",
"name": "Set Query",
"type": "n8n-nodes-base.set",
"position": [
-3264,
816
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "58609f1a-03c7-4e5a-8199-2fcdaf495325",
"name": "query",
"type": "array",
"value": "={{[\n\"n8n agents tutorial step by step\",\n\"AI tools tutorial for beginners\",\n\"Best AI agent building tutorial\",\n\"AI prompt engineering techniques\",\n\"Quick AI tools training\",\n\"llm prompt engineering best practices\"]}}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "77b7b60e-d8c6-431b-86e8-9ca1fc0bf8f3",
"name": "Search YouTube",
"type": "n8n-nodes-base.httpRequest",
"position": [
-2704,
816
],
"parameters": {
"url": "https://www.googleapis.com/youtube/v3/search",
"options": {},
"sendQuery": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "part",
"value": "id,snippet"
},
{
"name": "q",
"value": "={{ $json.query }}"
},
{
"name": "type",
"value": "video"
},
{
"name": "maxResults",
"value": "50"
},
{
"name": "order",
"value": "relevance"
},
{
"name": "videoCategoryId",
"value": "27"
},
{
"name": "videoDuration",
"value": "short"
}
]
},
"nodeCredentialType": "youTubeOAuth2Api"
},
"credentials": {
"youTubeOAuth2Api": {
"id": "Lfemt3uYHxqdzpXT",
"name": "YouTube account"
}
},
"typeVersion": 4.2
},
{
"id": "1d033bf5-0de6-4462-9b59-58a3ccba1ee0",
"name": "Filter for Quality",
"type": "n8n-nodes-base.filter",
"position": [
-1344,
1008
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "0fe9a82c-6c5c-44c1-8da7-24e2f8783a0e",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.statistics.viewCount }}",
"rightValue": 10000
},
{
"id": "7eae8c9b-c85d-4f0f-92bd-01b047e05d0e",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.statistics.likeCount }}",
"rightValue": 100
},
{
"id": "51d609d3-0b04-476e-9abd-a68a1f9c095c",
"operator": {
"type": "dateTime",
"operation": "after"
},
"leftValue": "={{ $json.snippet.publishedAt }}",
"rightValue": "2023-12-01T00:00:00"
},
{
"id": "8f67a03c-b596-459f-82ec-0f1dd9fa8009",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.DurationSeconds }}",
"rightValue": 61
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "c4fc8a8a-2658-490e-b0da-c3c7ac67fa07",
"name": "Filter for Relevance",
"type": "n8n-nodes-base.filter",
"position": [
-2256,
816
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "051c8268-ace1-4d87-a57e-798ed9992331",
"operator": {
"type": "string",
"operation": "regex"
},
"leftValue": "={{ $json.snippet.title.toLowerCase() }}",
"rightValue": "tutorial|course|guide|training|learn|education|explained|masterclass|complete.*guide|step.*by.*step|beginners.*guide|how.*to.*build|fundamentals"
},
{
"id": "3bb11116-7ac6-4aa1-b1ac-3ebd1a6ceb82",
"operator": {
"type": "string",
"operation": "notRegex"
},
"leftValue": "={{ $json.snippet.title.toLowerCase() }}",
"rightValue": "money|rich|profit|earn|cash|income|\\$|💰|sell|agency|business|client|freelance|side.*hustle|make.*money"
},
{
"id": "5edc6bdd-514c-4cbd-bbbc-51da2f87c41e",
"operator": {
"type": "string",
"operation": "notRegex"
},
"leftValue": "={{ $json.snippet.title.toLowerCase() }}",
"rightValue": "autopilot|passive.*income|[\\d]+k.*subs|faceless.*videos|youtube.*automation.*money|100%.*automated.*money"
},
{
"id": "320ed472-8b8d-49bc-b830-e95c39017397",
"operator": {
"type": "string",
"operation": "notRegex"
},
"leftValue": "={{ $json.snippet.title.toLowerCase() }}",
"rightValue": "insane|crazy|ultimate.*secret|hack|trick|must.*do|steal.*these|best.*way.*to.*make|ways.*to.*make|you.*need.*to|[\\d]+.*ways|[\\d]+.*insane"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "6d357a9a-642a-4c5d-9309-e9ffa416ec70",
"name": "Get Video Metadata",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1760,
1008
],
"parameters": {
"url": "https://www.googleapis.com/youtube/v3/videos",
"options": {},
"sendQuery": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "part",
"value": "snippet,statistics,contentDetails"
},
{
"name": "id",
"value": "={{ $json.id.videoId }}"
}
]
},
"nodeCredentialType": "youTubeOAuth2Api"
},
"credentials": {
"youTubeOAuth2Api": {
"id": "Lfemt3uYHxqdzpXT",
"name": "YouTube account"
}
},
"typeVersion": 4.2
},
{
"id": "347c1038-c088-4a9a-9e21-d0224482b066",
"name": "Remove Unnecessary Fields",
"type": "n8n-nodes-base.set",
"position": [
-1152,
1008
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "bd1dfa28-997f-45e7-983f-06b4afdf2224",
"name": "Title",
"type": "string",
"value": "={{ $json.snippet.title }}"
},
{
"id": "9921ef02-a86d-42ae-a4df-9c2dd674505c",
"name": "Channel",
"type": "string",
"value": "={{ $json.snippet.channelTitle }}"
},
{
"id": "ec38edef-66e2-480f-9617-66123decfcef",
"name": "Published At",
"type": "string",
"value": "={{ $json.snippet.publishedAt }}"
},
{
"id": "9bb69372-553a-4c8d-8286-00f41c90a78c",
"name": "Views",
"type": "string",
"value": "={{ $json.statistics.viewCount }}"
},
{
"id": "80edec74-08c1-4d68-82f7-ba061cb2173e",
"name": "Likes",
"type": "string",
"value": "={{ $json.statistics.likeCount }}"
},
{
"id": "9700bbee-7f25-43fe-a653-42a7c8fade77",
"name": "Description",
"type": "string",
"value": "={{ $json.snippet.description }}"
},
{
"id": "9afaaa69-1afd-4f45-bfe6-24671651ac82",
"name": "URL",
"type": "string",
"value": "=https://www.youtube.com/watch?v={{ $json.id }}"
},
{
"id": "da4e4ed8-b6b6-4e79-b7a5-655b5c3894e8",
"name": "Duration",
"type": "number",
"value": "={{ $json.DurationSeconds }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "96084e37-0f63-4a3a-a831-867c55aa0cfd",
"name": "Remove Duplicate Videos",
"type": "n8n-nodes-base.removeDuplicates",
"position": [
-1104,
800
],
"parameters": {
"compare": "selectedFields",
"options": {},
"fieldsToCompare": "id.videoId"
},
"typeVersion": 2
},
{
"id": "fd0f3127-76ad-4bd9-ae8e-51183858a8bc",
"name": "Generate Relevance Score",
"type": "n8n-nodes-base.set",
"position": [
-848,
800
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "e752a5e1-8485-4ef6-a717-d52234f3fb8c",
"name": "video_id",
"type": "string",
"value": "={{ $('Get Video Metadata').item.json.items[0].id }}"
},
{
"id": "06fdbb84-7a8f-4ef7-b972-a480c528146e",
"name": "DownloadURL",
"type": "string",
"value": ""
},
{
"id": "d691e3bf-9fc4-406f-b6e7-d62487820ea1",
"name": "RelevanceScore",
"type": "number",
"value": "={{\n // Educational keywords (higher scores)\n (($json.Title.toLowerCase().includes('tutorial')) ? 15 : 0) +\n (($json.Title.toLowerCase().includes('course')) ? 15 : 0) +\n (($json.Title.toLowerCase().includes('guide')) ? 12 : 0) +\n (($json.Title.toLowerCase().includes('explained')) ? 12 : 0) +\n (($json.Title.toLowerCase().includes('beginner')) ? 10 : 0) +\n (($json.Title.toLowerCase().includes('step by step')) ? 15 : 0) +\n (($json.Title.toLowerCase().includes('complete')) ? 10 : 0) +\n \n // AI-specific terms\n (($json.Title.toLowerCase().includes('prompt engineering')) ? 20 : 0) +\n (($json.Title.toLowerCase().includes('ai agent')) ? 18 : 0) +\n (($json.Title.toLowerCase().includes('ai tools')) ? 15 : 0) +\n (($json.Title.toLowerCase().includes('prompting')) ? 15 : 0) +\n \n // Quality indicators\n ((parseInt($json.Views) > 100000) ? 10 : 0) +\n ((parseInt($json.Views) > 50000) ? 5 : 0) +\n ((parseInt($json.Likes) > 1000) ? 8 : 0) +\n \n // Penalty for monetization indicators\n (($json.Title.toLowerCase().includes('money') || \n $json.Title.toLowerCase().includes('rich') || \n $json.Title.toLowerCase().includes('earn')) ? -20 : 0)\n}}"
},
{
"id": "a9e446de-dc9c-4dba-ab0b-d5647a24ba39",
"name": "Title",
"type": "string",
"value": "={{ $json.Title }}"
},
{
"id": "1669b129-9e9a-45e6-925f-c5e41e6de692",
"name": "Channel",
"type": "string",
"value": "={{ $json.Channel }}"
},
{
"id": "3d45d0ce-3068-424c-80a3-06c11785846d",
"name": "Published at",
"type": "string",
"value": "={{ $json['Published At'] }}"
},
{
"id": "d778036c-17fb-475b-a304-9e92e37bdce7",
"name": "Duration",
"type": "number",
"value": "={{ $json.Duration }}"
},
{
"id": "16e45f2b-1d0b-48e4-b86c-7e8ba82c5aef",
"name": "Views",
"type": "string",
"value": "={{ $json.Views }}"
},
{
"id": "86f2af64-27f9-4e3c-9883-c279edc938c6",
"name": "Likes",
"type": "string",
"value": "={{ $json.Likes }}"
},
{
"id": "d967b6c4-ab40-4f87-a84e-e0cb1bbb51d6",
"name": "Description",
"type": "string",
"value": "={{ $json.Description }}"
},
{
"id": "e0af7129-2215-4583-b236-63587a4974b6",
"name": "URL",
"type": "string",
"value": "={{ $json.URL }}"
},
{
"id": "99e55d4e-0841-4cb2-ae15-ee183c1f55e7",
"name": "fetch_id",
"type": "string",
"value": ""
},
{
"id": "c0bb50f5-d231-42fb-b37d-5065934130f9",
"name": "fetch_status",
"type": "string",
"value": ""
},
{
"id": "c726e06e-6dd8-4fda-9dc9-f43519fe671b",
"name": "fetch_error",
"type": "string",
"value": ""
}
]
}
},
"typeVersion": 3.4
},
{
"id": "d663a0ea-4b03-40ef-bbbf-82ea08016d22",
"name": "Send to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
-192,
448
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "RelevanceScore",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "RelevanceScore",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Title",
"type": "string",
"display": true,
"required": false,
"displayName": "Title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Channel",
"type": "string",
"display": true,
"required": false,
"displayName": "Channel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Published at",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Published at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Duration",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Duration",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Views",
"type": "string",
"display": true,
"required": false,
"displayName": "Views",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Likes",
"type": "string",
"display": true,
"required": false,
"displayName": "Likes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "URL",
"type": "string",
"display": true,
"required": false,
"displayName": "URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "video_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "video_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "DownloadURL",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "DownloadURL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "fetch_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "fetch_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "fetch_status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "fetch_status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "fetch_error",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "fetch_error",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s/edit#gid=0",
"cachedResultName": "YouTube AI Resources"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s/edit?usp=drivesdk",
"cachedResultName": "Eli_YouTube AI Resources"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "j0u6p2nyjo7aHG38",
"name": "Google Sheets account"
}
},
"typeVersion": 4.6
},
{
"id": "761eda1b-8403-4e22-a1a2-61a1977f13cd",
"name": "Add Duration Seconds",
"type": "n8n-nodes-base.code",
"position": [
-1552,
1008
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const video = $json.items?.[0]; // <-- THIS is the real video object\nconst isoRaw = video?.contentDetails?.duration;\n\nconst iso = String(isoRaw ?? \"PT0S\").trim();\n\nconst re = /^PT(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?$/;\nconst m = re.exec(iso);\n\nconst hours = m?.[1] ? parseInt(m[1], 10) : 0;\nconst minutes = m?.[2] ? parseInt(m[2], 10) : 0;\nconst seconds = m?.[3] ? parseInt(m[3], 10) : 0;\n\nreturn {\n // Flatten the video fields so the next nodes are easy:\n ...video,\n\n DurationSeconds: hours * 3600 + minutes * 60 + seconds\n};"
},
"typeVersion": 2
},
{
"id": "d25bdfd7-16d8-4b49-93e1-98008763613a",
"name": "Start Fetch (FetchMedia) (HTTP Request)",
"type": "n8n-nodes-base.httpRequest",
"position": [
16,
448
],
"parameters": {
"url": "https://api.fetchmedia.io/v1/fetch",
"method": "POST",
"options": {},
"sendBody": true,
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "url",
"value": "={{ $json.URL }}"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "X-API-KEY",
"value": "{{$env.FETCHMEDIA_API_KEY}}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "34ae2f83-aa8a-4359-bc72-c1b992d9fb3c",
"name": "Init Polling State (Set)",
"type": "n8n-nodes-base.set",
"position": [
224,
448
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "8c29eddd-6e12-424a-8350-c2f71c978cd4",
"name": "fetch_id",
"type": "string",
"value": "={{ $json.fetch_id }}"
},
{
"id": "c1785622-f22d-415b-9cec-cbb3e99b0cd2",
"name": "attempt",
"type": "number",
"value": 0
},
{
"id": "d0636c5b-03f1-45e6-86ef-e1e659ac0770",
"name": "video_id",
"type": "string",
"value": "={{ $('Take only the amount of top videos').item.json.video_id }}"
},
{
"id": "ebb66bb0-0bc0-4d67-9665-0d3f0796a11b",
"name": "fetch_status",
"type": "string",
"value": "pending"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "839f4997-97c0-4d0d-b500-18d56ebb9e25",
"name": "Poll loop (Wait → GET status → check → loop)",
"type": "n8n-nodes-base.wait",
"position": [
448,
448
],
"webhookId": "a743f132-903f-4e96-8d5b-e3fc3a050ee7",
"parameters": {
"amount": 15
},
"typeVersion": 1.1
},
{
"id": "3541fdc9-a262-47d1-bc69-8e364a2ad58b",
"name": "Check Fetch Status (HTTP Request)",
"type": "n8n-nodes-base.httpRequest",
"position": [
656,
448
],
"parameters": {
"url": "=https://api.fetchmedia.io/v1/fetch/{{ $('Init Polling State (Set)').item.json.fetch_id }}",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-API-KEY",
"value": "{{$env.FETCHMEDIA_API_KEY}}"
},
{
"name": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"name": "Accept",
"value": "*/*"
},
{}
]
}
},
"typeVersion": 4.2
},
{
"id": "e3bc6de3-75bd-4c5c-aef8-0c6edf7e6c96",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2976,
448
],
"parameters": {
"width": 736,
"height": 304,
"content": "### YouTube Discovery & Initial Filtering\n\n\nThis section searches YouTube using the YouTube Data API\nand applies initial relevance and quality filters.\n\nVideos are filtered by:\n- Title keywords\n- Publish date\n- Engagement signals (views and likes)\n\nOnly videos that pass these checks continue downstream."
},
"typeVersion": 1
},
{
"id": "386d1600-7d71-4b22-94a4-6ba524da2d54",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1648,
1232
],
"parameters": {
"width": 480,
"height": 272,
"content": "### Duration & Quality Validation\n\nYouTube returns video duration in ISO-8601 format (PT#M#S).\n\nThis step:\n- Converts duration to seconds\n- Enables numeric filtering (e.g. Shorts vs long-form)\n- Ensures only videos within the desired length range pass\n\nThis avoids unreliable string-based comparisons.\n"
},
"typeVersion": 1
},
{
"id": "fbd55eda-7596-409f-ab0b-cf52bba5614c",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1024,
480
],
"parameters": {
"width": 608,
"height": 240,
"content": "### Deduplication & Relevance Scoring\n\n\nThis section removes duplicate videos using videoId\nand assigns a relevance score based on engagement\nand metadata signals.\n\nVideos are then sorted by score and limited\nto the top results.\n\nDuplicates can happen since we're running multiple queries against YouTube search api\n"
},
"typeVersion": 1
},
{
"id": "13881be5-3f6d-478e-b686-3aa60da933f6",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
-224
],
"parameters": {
"width": 1088,
"height": 400,
"content": "## Asynchronous Download & Status Polling (FetchMedia)\n\nSelected videos are submitted to FetchMedia.io for download.\n\nBecause downloads are asynchronous:\n- The workflow polls the API at intervals\n- Status is checked (pending / running / success / failed)\n- Retries are capped to avoid infinite loops\n\nFinal download URLs or errors are written back to Google Sheets.\n\n**This workflow expects FETCHMEDIA_API_KEY to be set as an environment variable.\nIf not set, the download step will be skipped or fail gracefully.**\n\n\nhttps://docs.fetchmedia.io/api-introduction"
},
"typeVersion": 1
},
{
"id": "647e12dc-66a6-49a8-9def-c6126a5ed423",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1968,
560
],
"parameters": {
"width": 336,
"height": 240,
"content": "## Batch Processing Note\n\nThe Done branch of this loop is intentionally unused.\n\nAll processing occurs inside the batch loop\nto preserve item context and enable controlled retries."
},
"typeVersion": 1
},
{
"id": "e9d70b17-aabc-44a1-8075-313563ceeed5",
"name": "Loop over YouTube Results",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-2048,
816
],
"parameters": {
"options": {},
"batchSize": 5
},
"executeOnce": false,
"typeVersion": 3,
"alwaysOutputData": false
},
{
"id": "86d6ef5c-81f2-4f97-9cac-a66ff0c83049",
"name": "Split search queries",
"type": "n8n-nodes-base.splitOut",
"position": [
-2992,
816
],
"parameters": {
"options": {},
"fieldToSplitOut": "query"
},
"typeVersion": 1
},
{
"id": "c2dd49f2-7507-4d87-a2b2-bc93f99c9f87",
"name": "Split YouTube Results",
"type": "n8n-nodes-base.splitOut",
"position": [
-2464,
816
],
"parameters": {
"options": {},
"fieldToSplitOut": "items"
},
"typeVersion": 1
},
{
"id": "da59ced5-3853-49b8-9d27-836c2e2a0c6d",
"name": "Take only the amount of top videos",
"type": "n8n-nodes-base.limit",
"position": [
-416,
800
],
"parameters": {
"maxItems": 5
},
"typeVersion": 1
},
{
"id": "e45fddc2-32ea-4806-a13a-1bce27b89da7",
"name": "Sort by Relevance (Our score)",
"type": "n8n-nodes-base.sort",
"position": [
-624,
800
],
"parameters": {
"options": {
"disableDotNotation": false
},
"sortFieldsUi": {
"sortField": [
{
"order": "descending",
"fieldName": "RelevanceScore"
}
]
}
},
"typeVersion": 1
},
{
"id": "1a1664ba-0c66-4bcf-af3a-e87daaefdbe4",
"name": "Check max attempts",
"type": "n8n-nodes-base.if",
"position": [
480,
912
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "7e2888de-8186-4b07-93df-ab1e296413ee",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.attempt }}",
"rightValue": 31
}
]
}
},
"typeVersion": 2.2
},
{
"id": "03e48bda-9ca9-48f8-9884-b11966b75319",
"name": "Increase attempt counter",
"type": "n8n-nodes-base.code",
"position": [
1184,
1056
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "return {\n ...$json,\n attempt: ($json.attempt ?? 0) + 1\n};"
},
"typeVersion": 2
},
{
"id": "0c7a3b89-3ea0-4430-a7d6-b32f16555864",
"name": "Search for the relevant video",
"type": "n8n-nodes-base.googleSheets",
"position": [
688,
912
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $json.url }}",
"lookupColumn": "URL"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s/edit#gid=0",
"cachedResultName": "YouTube AI Resources"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s/edit?usp=drivesdk",
"cachedResultName": "Eli_YouTube AI Resources"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "j0u6p2nyjo7aHG38",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "53226a86-cb8a-492a-9903-4a5c135c3c32",
"name": "Update the status of the download",
"type": "n8n-nodes-base.googleSheets",
"position": [
896,
912
],
"parameters": {
"columns": {
"value": {
"video_id": "={{ $json.url }}",
"row_number": 0,
"DownloadURL": "=Issue with the download :-( "
},
"schema": [
{
"id": "RelevanceScore",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "RelevanceScore",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Title",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Channel",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Channel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Published at",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Published at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Duration",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Duration",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Views",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Views",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Likes",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Likes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "URL",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "video_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "video_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "DownloadURL",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "DownloadURL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"URL"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s/edit#gid=0",
"cachedResultName": "YouTube AI Resources"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s/edit?usp=drivesdk",
"cachedResultName": "Eli_YouTube AI Resources"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "j0u6p2nyjo7aHG38",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "8f76971a-92f0-42fd-828a-2507c8000df0",
"name": "Search for the relevant video1",
"type": "n8n-nodes-base.googleSheets",
"position": [
1056,
432
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $json.url }}",
"lookupColumn": "URL"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s/edit#gid=0",
"cachedResultName": "YouTube AI Resources"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s/edit?usp=drivesdk",
"cachedResultName": "Eli_YouTube AI Resources"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "j0u6p2nyjo7aHG38",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "da150c72-b361-4189-8ef4-45dea4ced79d",
"name": "Update the status and the download URL",
"type": "n8n-nodes-base.googleSheets",
"position": [
1232,
432
],
"parameters": {
"columns": {
"value": {
"URL": "={{ $json.URL }}",
"fetch_id": "={{ $('Check if download is complete').item.json.id }}",
"DownloadURL": "={{ $('Check if download is complete').item.json.download_url }}",
"fetch_error": "={{ $('Check if download is complete').item.json.error }}",
"fetch_status": "={{ $('Check if download is complete').item.json.status }}"
},
"schema": [
{
"id": "RelevanceScore",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "RelevanceScore",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Title",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Channel",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Channel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Published at",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Published at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Duration",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Duration",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Views",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Views",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Likes",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Likes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "URL",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "URL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "video_id",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "video_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "DownloadURL",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "DownloadURL",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "fetch_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "fetch_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "fetch_status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "fetch_status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "fetch_error",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "fetch_error",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"URL"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s/edit#gid=0",
"cachedResultName": "YouTube AI Resources"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1YxDDQ2-TH-Vw6KgG3kxwn1L4Fyttm8lFf200JGFl6-s/edit?usp=drivesdk",
"cachedResultName": "Eli_YouTube AI Resources"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "j0u6p2nyjo7aHG38",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "6e4fca81-19fc-4e96-9c90-c30a02cf747c",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1792,
-432
],
"parameters": {
"width": 800,
"height": 608,
"content": "## How it works\n\nThis workflow searches YouTube for educational videos using a predefined list of queries. It filters out low-quality or irrelevant results and enriches the remaining videos with additional metadata, including view count, like count, publish date, and video duration (converted to seconds).\n\nNext, the workflow scores and sorts the videos based on relevance and quality, keeps only the top results, and writes them to a Google Sheet. Optionally, the best videos are submitted to FetchMedia.io for downloading. The workflow continuously polls FetchMedia until each download is completed and then updates the same Google Sheet row using the YouTube video ID as the unique identifier.\n\n## Setup steps\n\n1. Connect **YouTube Data API** OAuth credentials and ensure the *YouTube Data API v3* is enabled in Google Cloud.\n2. Connect **Google Sheets** OAuth credentials and select the target spreadsheet and sheet.\n3. *(Optional)* Add your **FetchMedia.io API key** (recommended via environment variables) and verify the FetchMedia API endpoints.\n4. Update the search queries and filtering thresholds (duration, views, likes, publish date) to match your requirements.\n5. Run the workflow once and verify that rows are appended to Google Sheets and that download URLs are written back after completion."
},
"typeVersion": 1
},
{
"id": "e37e00be-3c8d-4396-951a-bf6855120ad7",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3312,
768
],
"parameters": {
"color": 3,
"width": 992,
"height": 192,
"content": "### Search YouTube + split results"
},
"typeVersion": 1
},
{
"id": "79b62512-61b7-404e-bf60-a1240ef3cf54",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2304,
768
],
"parameters": {
"color": 4,
"height": 192,
"content": "### Filter for relevance (title keywords / exclude spam)"
},
"typeVersion": 1
},
{
"id": "0e827385-25d0-4159-bd26-aa245d7ab6b8",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1872,
960
],
"parameters": {
"color": 5,
"width": 480,
"height": 208,
"content": "### Fetch video metadata + convert duration to seconds"
},
"typeVersion": 1
},
{
"id": "1d1fb890-746f-4cae-b3fe-545dc523ca40",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1376,
960
],
"parameters": {
"color": 2,
"width": 400,
"height": 208,
"content": "### Quality filter (views/likes/date/duration)"
},
"typeVersion": 1
},
{
"id": "eb1fcafe-8efd-4ab1-b39d-632f6a04f961",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
-928,
752
],
"parameters": {
"color": 6,
"width": 704,
"height": 224,
"content": "### Score + sort + limit top results"
},
"typeVersion": 1
},
{
"id": "9bed9cdd-44be-4cbd-94e6-3e1df2e7181f",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
-256,
368
],
"parameters": {
"color": 7,
"width": 1712,
"height": 256,
"content": "### Write to Sheets + download + poll status + update row"
},
"typeVersion": 1
},
{
"id": "e9d2c28a-4bff-49b2-9e8b-fecdf6e70cf3",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
240,
800
],
"parameters": {
"width": 1216,
"height": 464,
"content": "### If the FetchMedia service is unresponsive, we limit the number of retry attempts to 30.\nAfter reaching this threshold, the process will stop and the corresponding row in the Google Sheet will be updated with a “Failed” status."
},
"typeVersion": 1
},
{
"id": "627cf3d4-2e18-4a95-b3fc-14dd8a78e039",
"name": "Check if download is complete",
"type": "n8n-nodes-base.if",
"position": [
848,
448
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b91739ff-bac6-492e-b282-e52ef1faac9e",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.status }}",
"rightValue": "success"
}
]
}
},
"typeVersion": 2.2
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "789b3114-237a-4321-b783-969835216f53",
"connections": {
"Set Query": {
"main": [
[
{
"node": "Split search queries",
"type": "main",
"index": 0
}
]
]
},
"Search YouTube": {
"main": [
[
{
"node": "Split YouTube Results",
"type": "main",
"index": 0
}
]
]
},
"Check max attempts": {
"main": [
[
{
"node": "Poll loop (Wait → GET status → check → loop)",
"type": "main",
"index": 0
}
],
[
{
"node": "Search for the relevant video",
"type": "main",
"index": 0
}
]
]
},
"Filter for Quality": {
"main": [
[
{
"node": "Remove Unnecessary Fields",
"type": "main",
"index": 0
}
]
]
},
"Get Video Metadata": {
"main": [
[
{
"node": "Add Duration Seconds",
"type": "main",
"index": 0
}
]
]
},
"Add Duration Seconds": {
"main": [
[
{
"node": "Filter for Quality",
"type": "main",
"index": 0
}
]
]
},
"Filter for Relevance": {
"main": [
[
{
"node": "Loop over YouTube Results",
"type": "main",
"index": 0
}
]
]
},
"Split search queries": {
"main": [
[
{
"node": "Search YouTube",
"type": "main",
"index": 0
}
]
]
},
"Send to Google Sheets": {
"main": [
[
{
"node": "Start Fetch (FetchMedia) (HTTP Request)",
"type": "main",
"index": 0
}
]
]
},
"Split YouTube Results": {
"main": [
[
{
"node": "Filter for Relevance",
"type": "main",
"index": 0
}
]
]
},
"Remove Duplicate Videos": {
"main": [
[
{
"node": "Generate Relevance Score",
"type": "main",
"index": 0
}
]
]
},
"Generate Relevance Score": {
"main": [
[
{
"node": "Sort by Relevance (Our score)",
"type": "main",
"index": 0
}
]
]
},
"Increase attempt counter": {
"main": [
[
{
"node": "Check max attempts",
"type": "main",
"index": 0
}
]
]
},
"Init Polling State (Set)": {
"main": [
[
{
"node": "Poll loop (Wait → GET status → check → loop)",
"type": "main",
"index": 0
}
]
]
},
"Loop over YouTube Results": {
"main": [
[
{
"node": "Remove Duplicate Videos",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Video Metadata",
"type": "main",
"index": 0
}
]
]
},
"Remove Unnecessary Fields": {
"main": [
[
{
"node": "Loop over YouTube Results",
"type": "main",
"index": 0
}
]
]
},
"Check if download is complete": {
"main": [
[
{
"node": "Search for the relevant video1",
"type": "main",
"index": 0
}
],
[
{
"node": "Increase attempt counter",
"type": "main",
"index": 0
}
]
]
},
"Search for the relevant video": {
"main": [
[
{
"node": "Update the status of the download",
"type": "main",
"index": 0
}
]
]
},
"Sort by Relevance (Our score)": {
"main": [
[
{
"node": "Take only the amount of top videos",
"type": "main",
"index": 0
}
]
]
},
"Search for the relevant video1": {
"main": [
[
{
"node": "Update the status and the download URL",
"type": "main",
"index": 0
}
]
]
},
"Check Fetch Status (HTTP Request)": {
"main": [
[
{
"node": "Check if download is complete",
"type": "main",
"index": 0
}
]
]
},
"Take only the amount of top videos": {
"main": [
[
{
"node": "Send to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"When clicking ‘Execute workflow’": {
"main": [
[
{
"node": "Set Query",
"type": "main",
"index": 0
}
]
]
},
"Start Fetch (FetchMedia) (HTTP Request)": {
"main": [
[
{
"node": "Init Polling State (Set)",
"type": "main",
"index": 0
}
]
]
},
"Poll loop (Wait → GET status → check → loop)": {
"main": [
[
{
"node": "Check Fetch Status (HTTP Request)",
"type": "main",
"index": 0
}
]
]
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment