DEV Community

Grewup
Grewup

Posted on

N8N Workflow That Auto-Creates Viral YouTube Shorts While You Sleep

What this builds

A fully automated faceless YouTube Shorts pipeline. You add a topic. The workflow:

Reads your topic from Google Sheets
Generates a short-form script optimised for vertical video (GPT-4 via OpenRouter)
Creates AI image prompts for each scene
Sends structured content to JSON2Video for vertical video rendering
Polls render status until complete
Downloads the MP4
Uploads directly to YouTube as a Short
Updates your Sheet with the video URL

Target output: 60-second vertical Shorts, fully faceless, 9:16 format, rendered and uploaded without any manual steps.
Architecture

Schedule Trigger (daily or on-demand)
        ↓
Google Sheets — read first "to-do" row
        ↓
Script Agent — GPT-4 generates hook + body + CTA + scene image prompts
        ↓
JSON2Video API — POST with vertical template → returns project_id
        ↓
Wait 90s → GET render status → Switch (done / running / error)
        ↓ [loop until done]
HTTP Request — download MP4 as binary file
        ↓
YouTube node — upload as Short (9:16, under 60s)
        ↓
Google Sheets — update status + video URL
Enter fullscreen mode Exit fullscreen mode

Step 1 — Google Sheet setup

Create a new Google Sheet with these exact column headers:
Topic | Script Status | Upload Status | Video URL
Add topics. Set Script Status to to-do for each. Examples:
5 AI tools that save 10 hours a week | to-do | to-do |
Why your HeyGen videos look robotic | to-do | to-do |
GoHighLevel vs HubSpot — who wins | to-do | to-do |
The workflow processes one row per run — only rows where Script Status equals to-do.

Step 2 — Google Sheets node

Add a Google Sheets node as your trigger.
Operation: Get Rows
Filter: Script Status = "to-do"
Limit: 1 (first matching row only)
Critical: toggle "Return only first matching row" ON. Without this, the workflow attempts to process your entire backlog in one run — GPU costs spiral and the workflow times out.

Step 3 — Script Agent (GPT-4 via OpenRouter)

This is the node that determines whether your Short will perform or get skipped. The script structure for Shorts is different from long-form — hooks must be under 3 seconds of spoken word, and every scene needs to visually match the spoken content.
Add an AI Agent node. Connect OpenRouter credentials.
System prompt:
You are an expert YouTube Shorts scriptwriter.
You write addictive, fast-paced scripts that hook viewers in under 3 seconds.
Always structure scripts as: Hook → Problem → Solution → Proof → CTA
Keep total script under 60 seconds of spoken content.
Return ONLY clean JSON, no backticks, no explanation.
User message:
Write a YouTube Shorts script about: "{{ $json.Topic }}"

Return this exact JSON structure:

{
  "hook": "First spoken line — must create immediate curiosity or shock. Under 8 words.",
  "scenes": [
    {
      "spoken": "Spoken content for this scene (2-3 sentences max)",
      "image_prompt": "Visual scene description for AI image generation. Photorealistic, specific."
    }
  ],
  "cta": "Final call to action line. Under 10 words.",
  "title": "YouTube title optimised for search. Under 60 characters.",
  "hashtags": ["hashtag1", "hashtag2", "hashtag3"]
}
Enter fullscreen mode Exit fullscreen mode

Create exactly 4 scenes plus the hook. Total spoken content must be under 60 seconds.
Model: openai/gpt-4o-mini via OpenRouter — approximately $0.001 per script.
Enable "Require specific output format" — this enforces the JSON schema and prevents GPT from returning narrative text.
Execute the node and verify: you should see a JSON object with a hook, scenes array of 4 objects, cta, title, and hashtags. If the schema breaks, add this to your prompt: "Double-check you have exactly 4 scene objects before returning."

Step 4 — JSON2Video API (render the Short)

Add an HTTP Request node.
Method: POST
URL: https://api.json2video.com/v2/movies
Headers:
x-api-key: [your JSON2Video API key]
Content-Type: application/json
Critical — use a vertical template. JSON2Video has both landscape and vertical templates. For Shorts you must use a 9:16 vertical template. Create or duplicate one in your JSON2Video dashboard before this step. Note the template ID.
Build your request body using expressions from the Script Agent output:

json{
  "template": "[your_vertical_template_id]",
  "variables": {
    "hook_text": "{{ $json.hook }}",
    "scene1_spoken": "{{ $json.scenes[0].spoken }}",
    "scene1_image": "{{ $json.scenes[0].image_prompt }}",
    "scene2_spoken": "{{ $json.scenes[1].spoken }}",
    "scene2_image": "{{ $json.scenes[1].image_prompt }}",
    "scene3_spoken": "{{ $json.scenes[2].spoken }}",
    "scene3_image": "{{ $json.scenes[2].image_prompt }}",
    "scene4_spoken": "{{ $json.scenes[3].spoken }}",
    "scene4_image": "{{ $json.scenes[3].image_prompt }}",
    "cta_text": "{{ $json.cta }}"
  }
}
Enter fullscreen mode Exit fullscreen mode

The API returns a project_id immediately. Save this in a Set node for the polling loop.

Step 5 — Status polling loop

Shorts render faster than long-form videos — typically 2–5 minutes. The polling loop handles this.
Node sequence:
Wait node (90 seconds)

HTTP Request GET → https://api.json2video.com/v2/movies?project={{ $json.project_id }}

Switch node
status = "done" → output 1 → continue
status = "running" → output 2 → Wait 15s → loop back
status = "preparing" → output 2 → Wait 15s → loop back

anything else → output 3 → update Sheet "error" → stop
Use a 90-second initial wait (shorter than long-form because Shorts render faster) and a 15-second loop interval.

Step 6 — Download the MP4

Method: GET
URL: {{ $json.video_url }}
Response format: File ← CRITICAL — must be set to File, not Auto-detect
This is the same critical setting from the Instagram automation and the Top 10 Videos workflow. It is under Add Option → Response → Response Format. Without it you receive JSON metadata instead of binary video data. The YouTube upload will fail silently.

Step 7 — YouTube upload

Add a YouTube node.
Operation: Upload Video
Title: {{ $('Script Agent').first().json.title }}
Description: {{ $('Script Agent').first().json.hashtags.join(' ') }}
Category: Science & Technology (or your niche)
Privacy: unlisted
Binary data: toggle ON
YouTube Shorts detection: YouTube automatically identifies a video as a Short if it is vertical (9:16) and under 60 seconds. You do not need to add #Shorts manually — the format handles it. Adding #Shorts to the description is optional but can help discovery in the first 24 hours.
Auth setup: You will need the YouTube Data API v3 enabled in Google Cloud Console. In your OAuth app settings, add your n8n redirect URI to the list of authorised redirect URIs. Connect the credential in n8n and authorise it.

Step 8 — Update Google Sheet

Operation: Update Row
Match on: Topic = {{ $('Google Sheets').first().json.Topic }}
Update:
Script Status → "created"
Upload Status → "unlisted"
Video URL → https://youtube.com/watch?v={{ $json.id }}
On the next workflow run, this row is skipped. Add new topics with Script Status to-do whenever you want more content.

What breaks

JSON2Video template mismatch:

If your variable names in the POST body do not match the variable names in your JSON2Video template, the render succeeds but the output is empty scenes. Log your template variable names exactly and match them in the n8n expression.

YouTube Short not recognised as a Short: Your template is not 9:16, or the video is over 60 seconds of content. Check your JSON2Video template dimensions and total scene duration.
Script Agent returns wrong scene count: Add to prompt: "You MUST return exactly 4 scene objects in the scenes array. Count before returning." GPT-4 mini occasionally returns 3 or 5 without this constraint.
Google Sheets filter stops working: Trailing space in the to-do cell value. Check with =LEN(A2) — if it returns more than 5, there is a hidden character.

Scaling this up

Once the single-video flow runs cleanly:
Replace the Schedule Trigger with a daily trigger set to 9 AM. Your Sheet becomes a content queue — add topics whenever inspiration hits, the workflow empties the queue daily.
For batch processing multiple Shorts per day, change the Sheets Limit from 1 to 3. Run the workflow three times per day via a cron trigger. Watch your API costs — JSON2Video charges per render.

Full article and workflow diagram at elevoras.com.
Building a variation of this? Drop your topic niche and node count in the comments.

Top comments (0)