n8n tutorial - Lesson 15: AI-Powered YouTube Title SEO with n8n

Hi everyone, in this post we're building a fully automated YouTube title SEO pipeline using n8n — the AI reads your existing video titles, suggests optimized versions with better keywords and CTR, queues them for review in a Google Sheet, then a second workflow pushes approved titles live to YouTube. This is Session 15 of our n8n Workflow Automation Tutorial series, and it's one of the most practical examples of n8n YouTube SEO automation you can build right now.

How to do:

Step 1 — Create the Google Sheet for Title Suggestions

Before building any nodes, set up the review Sheet that acts as the state machine between the two workflows.
  1. Create a new Google Sheet named T5-Title-Suggestions with a tab called Suggestions.
  2. Add exactly 9 columns in this order: video_id, video_url, current_title, view_count, published_at, suggested_title, reason, status, created_at.
  3. The status column drives the state machine — it accepts four values:
    • pending_review — AI has suggested a title, awaiting human decision
    • approved — human approved, ready for the updater workflow to push live
    • rejected — human rejected, the updater workflow skips this row
    • updated — title has been successfully pushed to YouTube
  4. Note your Sheet ID from the URL — you'll paste it into every Google Sheets node in both workflows.

Step 2 — Build Workflow 1: T5-B3-Title-SEO (Schedule + Idempotent Guard)

Create the main workflow named T5-B3-Title-SEO and set up the first three nodes that handle scheduling and duplicate prevention.
  1. Add a Schedule Trigger node. Set it to run Weekly on Sunday at 10:00.
  2. Add a Google Sheets node named Get Existing Video IDs:
    • Operation: Get Many, range A:A
    • Go to Settings tab → enable Always Output Data
    • This setting is critical — if the Sheet is empty on first run, n8n will stop the chain by default; Always Output Data forces execution to continue with zero items instead of halting.
  3. Add a Code node named Aggregate IDs. Write a short script that collects all video_id values from the previous node into a single array called existing_ids, then outputs it as one item.

Production tip — Any node that reads a "reference list" at the top of a pipeline (lookup data, not core flow data) must have Always Output Data enabled. This same fix was applied retroactively to the T5-B2-Comment-Pipeline from Session 14 after discovering the same empty-Sheet bug.

Step 3 — Fetch Videos from YouTube and Filter Early

Fetch the latest 50 videos from the channel, then filter out already-processed ones before any AI call to save token costs.
  1. Add an HTTP Request node named Get All Videos:
    • URL: https://www.googleapis.com/youtube/v3/search
    • Parameters: part=id,snippet, channelId={your_channel_id}, order=date, maxResults=50, type=video
    • Use your YouTube OAuth2 (HTTP) credential.
  2. Add a Split Out node named Split Videos. Set Field To Split Out to items — this unpacks the wrapped Google API response (one item containing an array) into individual video items.
  3. Add a Code node named Filter New Videos. For each item, read $json.id.videoId and drop any whose ID already exists in the existing_ids array from the Aggregate IDs node.

Note — Google APIs wrap list responses in a metadata envelope: {kind, etag, pageInfo, nextPageToken, items}. This means the HTTP node returns one n8n item containing all videos inside items[]. The Split Out node is what "opens" that envelope into N individual items. When dragging id.videoId from the Expression Editor, expand the id object first, then drag the videoId child — dragging the parent id gives you an object, not a string.

Production tip — Placing the idempotent filter before the AI node (not after) is the key cost-saving improvement in this session versus Session 14. Videos already in the Sheet never reach the AI, so you only pay for genuinely new content.

Step 4 — Fetch Video Stats and Flatten Data

For each new video, fetch full statistics and flatten everything into a clean 5-field object for the AI.
  1. Add an HTTP Request node named Get Video Stats:
    • URL: https://www.googleapis.com/youtube/v3/videos
    • Parameters: part=snippet,statistics, id={{ $json.id.videoId }}
    • This runs once per video (fan-out pattern), so each item from the filter step triggers its own API call.
  2. Add a Split Out node named Split Stats, splitting on items again — the videos.list endpoint also returns a wrapped response.
  3. Add a Code node named Flatten Video. Output exactly 5 fields:
    • video_id — from $json.id (note: videos.list returns id as a top-level string, unlike search.list which nests it as id.videoId)
    • video_url — constructed as https://www.youtube.com/watch?v={video_id}
    • current_title — from $json.snippet.title
    • view_count — from $json.statistics.viewCount, parsed with parseInt() because the API returns it as a string
    • published_at — from $json.snippet.publishedAt

Step 5 — AI Title Suggestion with Claude

Connect a Basic LLM Chain node to generate optimized title suggestions for each video.
  1. Add a Basic LLM Chain node named Suggest Title. Configure the model:
    • Model: Claude Haiku 4.5
    • Temperature: 0.7
    • Max tokens: 400
  2. Write the system prompt using an XML 4-block structure:
    • Block 1 — Role: Define the AI as a YouTube SEO title optimizer focused on keyword density and CTR.
    • Block 2 — Rules: Keep the same language as the original title, add the year 2026 where relevant, use power words, stay under 70 characters.
    • Block 3 — Few-shot examples: Include 3 examples — one Vietnamese cooking title (phở bò), one English template/resource title, one Vietnamese n8n automation title. This trains the model to preserve language and style.
    • Block 4 — Task: Pass {{ $json.current_title }} and {{ $json.view_count }} and ask for one optimized title with a reason.
  3. Add an Output Parser with a JSON schema defining two fields: suggested_title (string) and reason (string).

Tip — The few-shot examples are what keep the AI from switching languages. Without them, Claude may translate Vietnamese titles into English. With 3 bilingual examples, the model consistently preserves the original language while adding SEO improvements like year tags and high-CTR power words.

Step 6 — Build the 9-Column Row and Append to Sheet

The AI node drops the upstream fields, so you must use cross-node references to reconstruct the full row.
  1. Add an Edit Fields node named Build Row. Map all 9 columns:
    • For fields that came from before the AI node (video_id, video_url, current_title, view_count, published_at), use cross-node syntax: {{ $('Flatten Video').item.json.video_id }} — replace video_id with the relevant field name for each.
    • For AI output fields (suggested_title, reason), use {{ $json.suggested_title }} and {{ $json.reason }} — these come from the current item flowing through the chain.
    • Set status to the fixed string pending_review.
    • Set created_at to {{ $now.toISO() }}.
  2. Add a Google Sheets node named Append to Suggestions:
    • Operation: Append
    • Select your T5-Title-Suggestions Sheet and Suggestions tab
    • Mapping: Auto-Map — n8n matches column headers to field names automatically

Note — The reason cross-node reference is required here is that the Basic LLM Chain node replaces the current item's fields with only its own output — the upstream video data is dropped from $json. The syntax $('Flatten Video').item.json.X jumps back up the chain to retrieve those fields. Use $json.X (no node name tag) only when reading from the node directly connected via wire; use $('Node Name').item.json.X when crossing over a node that drops fields.

Step 7 — Build Workflow 2: T5-B3b-Title-Updater

Create the second workflow that runs every hour, picks up approved rows, pushes the new title to YouTube, and marks the row as updated.
  1. Create a new workflow named T5-B3b-Title-Updater. Add a Schedule Trigger set to run every 1 hour.
  2. Add a Google Sheets node named Get Approved Rows:
    • Operation: Get Many, range A:I
    • Add a filter: status equals approved
    • Enable Always Output Data — if no rows are approved yet, the workflow should exit cleanly instead of throwing an error.
  3. Add a YouTube node named Update Video Title:
    • Credential: YouTube (Personal) service-specific credential
    • Operation: Update a video
    • Video ID: {{ $json.video_id }}
    • Title: {{ $json.suggested_title }}
    • Region Code: set to your target region (e.g., Vietnam) — this field is required, the API call will fail without it
    • Category Name or ID: select the appropriate category (e.g., People & Blogs) — also required
  4. Add a Google Sheets node named Mark as Updated:
    • Operation: Update Row
    • Set Column to Match On to video_id
    • Set status to updated

Note — The YouTube videos.update API is technically PUT-style, meaning it normally requires you to send the full snippet object or it overwrites missing fields with empty values. The n8n YouTube node abstracts this entirely — it fetches the existing snippet first, merges your changes in, then sends the complete object. This means your video's description, tags, and other metadata are safe when you only change the title. This behavior was verified with a live video test in this session.

Step 8 — Activate Both Workflows and Test

With both workflows built, activate them and run an end-to-end test.
  1. Open T5-B3-Title-SEO and click Activate in the top-right. It will run automatically next Sunday at 10:00, or you can click Execute Workflow to test immediately.
  2. After a test run, open your T5-Title-Suggestions Sheet and verify:
    • New rows appeared with status = pending_review
    • All 9 columns are populated correctly
    • Videos already in the Sheet from a previous run are not duplicated
  3. Manually change one row's status from pending_review to approved in the Sheet.
  4. Open T5-B3b-Title-Updater, click Activate, then Execute Workflow. Verify:
    • The approved video's title changed on YouTube
    • The video's description and tags are unchanged
    • The row in the Sheet now shows status = updated

Tip — After activating, also check the Executions tab for both workflows the next morning to confirm no errors occurred during unattended runs. Catching issues early (like credential expiry or quota limits) is much easier when you review execution history within 24 hours of going live.

Key Lessons from This Session

  1. Always Output Data is required for reference-data nodes at the top of a pipeline. Any Sheets Get Many node that reads a lookup list (not the core flow item) must have this setting enabled, or an empty Sheet will silently halt the entire workflow.
  2. Place the idempotent filter before the AI node, not after. Filtering already-processed IDs early means only genuinely new videos reach the LLM, directly reducing API token costs per run.
  3. Google APIs return "wrapped" responses — one n8n item containing an items[] array. Always follow an HTTP Request node that calls a Google list endpoint with a Split Out node targeting items to unpack it into individual records.
  4. Use $('Node Name').item.json.X for cross-node references when a middle node drops fields. LLM Chain nodes replace the current item with their own output, so upstream fields must be retrieved by name, not via $json.
  5. The YouTube Update node requires both Region Code and Category Name/ID — they are not optional. Omitting either field causes the API call to fail even though the node UI does not mark them as required.
  6. The videos.list API returns id as a top-level string, unlike search.list which nests it as id.videoId. Always check which endpoint you're reading from before mapping the video ID field.
  7. The statistics.viewCount field from the YouTube API is a string, not a number. Wrap it with parseInt() in your Flatten code node to avoid downstream sorting or math errors.

Conclusion:

In this session of our n8n workflow automation tutorial series, we built a complete n8n YouTube SEO automation system — a 12-node AI pipeline that suggests optimized titles weekly, plus a 4-node updater that pushes approved changes live without touching your video descriptions or tags. The key architectural wins are the early idempotent filter that cuts AI costs, the cross-node reference pattern for post-LLM field recovery, and the two-workflow state machine that keeps a human in the loop via Google Sheets. In the next session (Session 16), we'll build a weekly performance analytics pipeline that pulls video statistics, runs AI-generated insights, and delivers a report to Telegram.

If you have any questions, feel free to leave a comment below. Thank you!

Tags: n8n youtube SEO automation, n8n tutorial, n8n workflow automation, YouTube title optimization, n8n AI automation, Google Sheets n8n, n8n LLM Chain, YouTube API n8n

Maybe you are interested!