ChatGPTSalesRevenue growthAI score · 73/100AI-assisted edit · no obvious prompt-injection riskn8n.io

Auto-generate UGC product videos from Google Sheets with DALL·E, GPT-4 Vision, and Sora in n8n

Input: Google Sheets product list. Processing: DALL·E 3 image → GPT-4 Vision style analysis → Sora video generation → Google Drive upload. Output: completed rows with public links.

ExperiencedStepsWorkflowPromptTip
Who
Marketing ops or automation engineers with an n8n instance and experience configuring OAuth credentials
You get
Automatically generate AI images and short videos for each product marked Pending, writing public links back to the sheet
You need
n8n · google-sheets · google-drive · dalle-3

Constraints

Prerequisites: n8n instance; Google Sheets OAuth2, Google Drive OAuth2, and OpenAI API credentials; a Google Sheet with columns Product Name / Description / Target Audience / Status / Image URL / Video URL / Error Message; Sora API access may be restricted. Costs: DALL·E 3 and Sora are usage-based.

What they did

Author vonnairone published an official n8n template demonstrating how to use a Google Sheet as the data source to automatically generate UGC-style videos for each product marked Pending. For each row, the workflow calls DALL·E 3 to generate an image, GPT-4 Vision to analyze visual style, and Sora to produce an 8-second 720x1280 video, then uploads to Google Drive and writes public links back to the sheet. The 37-node workflow includes scheduled triggering, batch looping, async polling, and error handling.

How it went

The template claims a "fully hands-off UGC video pipeline" (author_report). No specific success rate, generation time distribution, or cost data is provided by the source.

Worth knowing

Sora API access may be region-restricted or limited; video generation is asynchronous and can take several minutes per item; DALL·E and Sora are usage-based, so costs depend on volume; the template workflow.json contains hardcoded example Google Sheet and Drive Folder IDs that must be replaced with the user's own resources; generated image URLs may expire, which the template mitigates by persisting to Drive/Sheets; this site has not tested the workflow, reproduction=not_tested.

n8n workflow template

Structure checked · Not run-tested

Use “Import from File” in n8n, or copy the JSON and paste it into the editor. Connect your own accounts, then try test data. The template is inactive by default.

Setup and test checklist

Credentials to connect

  • Update Sheet — Error node: connect Google Sheets OAuth2 credentials.
  • Update Sheet — Done2 node: connect Google Sheets OAuth2 credentials.
  • Make File Public1 node: connect Google Drive OAuth2 credentials.
  • Upload Video to Google Drive1 node: connect Google Drive OAuth2 credentials.
  • Fetch Sora Video Content1 node: connect OpenAI API credentials.
  • Poll Sora Status1 node: connect OpenAI API credentials.
  • Sora Generate Video1 node: connect OpenAI API credentials.
  • GPT-4 Vision Analysis1 node: connect OpenAI API credentials.
  • DALL-E 3 Generate Image1 node: connect OpenAI API credentials.
  • Read Product Sheet1 node: connect Google Sheets OAuth2 credentials.

Configuration to replace

  • Source template is n8n public workflow #14256 (~37 nodes). Keep inactive after import; verify triggers, expressions and external API endpoints.
  • Sticky-note guidance is informational; replace placeholders with your own accounts, sheet schemas or channel IDs.

Test input and expected output

  • Manually run the main path once in a test workspace and confirm credentialed nodes authenticate without errors.
  • Drive one synthetic/redacted sample through and check output fields and downstream writes before enabling in production.

Template changes

  • Author credential bindings, webhookId and execution data are removed; the workflow is inactive. Nodes, connections and original prompts are preserved. Not run-tested in n8n.
  • No business-logic edits; verify keys, quotas and privacy compliance before use.

Template retrieved: 2026-10-25

Steps

  1. Trigger and read pending products

    Configure a Schedule Trigger in n8n to run on an interval. Add a Google Sheets node to read all rows from the Products sheet, then use a Code node to filter rows where Status = "Pending" and record each row’s sheetRowNumber (i + 2, accounting for the header). If no rows are Pending, the workflow ends naturally.

    Setup guidance · Source text。n8n Schedule Trigger + Google Sheets node setup guidance

    ## Stage 1 — Trigger & data fetch. 
    
    A schedule trigger fires on a set interval. It reads all rows from a Google Sheets "Products" tab, then a code node filters down to only rows where Status = "Pending", tracking each row number for later updates.
  2. Generate product image with DALL·E 3

    For each pending product, a Code node (AI Prompt Builder1) assembles a UGC-style prompt and calls the OpenAI Images Generations API (model: dall-e-3, size: 1024x1024, quality: hd, style: natural). A downstream Code node (Extract DALL-E URL1) extracts the imageUrl from the response and carries all product data forward.

    Execution prompt · Source text。n8n Code node AI Prompt Builder1 JS Code field

    // Build a UGC-style image generation prompt from product data
    const item = $input.first().json;
    
    const productName = item.productName || 'product';
    const description = item.description || '';
    const targetAudience = item.targetAudience || 'young adults';
    
    // Construct a detailed DALL-E 3 prompt with UGC aesthetic
    const prompt = [
      `A lifestyle photograph of ${productName} being used by ${targetAudience}.`,
      description ? `The product is ${description}.` : '',
      'Shot on iPhone, natural window lighting, casual home or cafe setting.',
      'UGC aesthetic: authentic, unposed, relatable feel.',
      'Warm color grading, shallow depth of field, genuine emotion.',
      'No text overlays, no logos, no watermarks.',
      'Photorealistic, editorial quality, candid moment.'
    ].filter(Boolean).join(' ');
    
    return [{
      json: {
        ...item,
        imagePrompt: prompt
      }
    }];

    Execution prompt · Source text。n8n HTTP Request node DALL-E 3 Generate Image1 JSON Body field

    ={{ JSON.stringify({
      model: "dall-e-3",
      prompt: $json.imagePrompt,
      n: 1,
      size: "1024x1024",
      quality: "hd",
      style: "natural"
    }) }}

    Execution prompt · Source text。n8n Code node Extract DALL-E URL1 JS Code field

    // Extract the DALL-E image URL directly and carry forward all product data
    const response = $input.first().json;
    const prevData = $('AI Prompt Builder1').first().json;
    
    const imageUrl = response.data?.[0]?.url || response.data?.[0]?.revised_url || '';
    
    if (!imageUrl) {
      throw new Error('No image URL returned from DALL-E 3: ' + JSON.stringify(response));
    }
    
    return [{
      json: {
        ...prevData,
        imageUrl: imageUrl,
        dalleRevisedPrompt: response.data?.[0]?.revised_prompt || ''
      }
    }];
  3. Analyze image with GPT-4 Vision and build Sora script

    An HTTP Request node calls GPT-4o (model: gpt-4o) for vision analysis: a system prompt requests JSON output (mood / dominantColors / composition / lighting / subjectExpression / settingDetails / suggestedCameraMovements / emotionalTone), and the user message includes the product name and imageUrl (detail: high). A downstream Code node (Parse Vision Analysis1) cleans and parses the JSON, then passes it to Video Script Builder1 to assemble a Sora-optimized video prompt.

    Execution prompt · Source text。n8n HTTP Request node GPT-4 Vision Analysis1 JSON Body field

    ={{ JSON.stringify({
      model: "gpt-4o",
      messages: [
        {
          role: "system",
          content: "You are a visual analyst for UGC video production. Analyze the image and return a JSON object with these fields: mood (string), dominantColors (array of strings), composition (string describing layout), lighting (string), subjectExpression (string), settingDetails (string), suggestedCameraMovements (array of strings for video), emotionalTone (string). Be specific and cinematic in your descriptions."
        },
        {
          role: "user",
          content: [
            {
              type: "text",
              text: "Analyze this UGC product image for video production planning. Product: " + $json.productName + ". Return structured JSON only, no markdown."
            },
            {
              type: "image_url",
              image_url: {
                url: $json.imageUrl,
                detail: "high"
              }
            }
          ]
        }
      ],
      max_tokens: 1000,
      temperature: 0.3
    }) }}

    Execution prompt · Source text。n8n Code node Parse Vision Analysis1 JS Code field

    // Parse GPT-4 Vision analysis and carry forward
    const input = $input.first().json;
    const prevData = $('Extract DALL-E URL1').first().json;
    
    const content = input?.choices?.[0]?.message?.content || '{}';
    
    let visionAnalysis;
    try {
      // Strip markdown code fences if present
      const cleaned = content.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
      visionAnalysis = JSON.parse(cleaned);
    } catch (e) {
      // If JSON parsing fails, use raw text
      visionAnalysis = {
        mood: 'warm and authentic',
        dominantColors: ['warm tones'],
        composition: content,
        lighting: 'natural',
        subjectExpression: 'genuine',
        settingDetails: 'casual setting',
        suggestedCameraMovements: ['slow zoom in', 'pan across product'],
        emotionalTone: 'relatable'
      };
    }
    
    return [{
      json: {
        ...prevData,
        visionAnalysis: visionAnalysis
      }
    }];

    Execution prompt · Source text。n8n Code node Video Script Builder1 JS Code field

    // Build a structured UGC video script for OpenAI Sora
    const item = $input.first().json;
    
    const product = item.productName || 'this product';
    const description = item.description || '';
    const audience = item.targetAudience || 'young adults';
    const vision = item.visionAnalysis || {};
    
    const mood = vision.mood || 'warm and authentic';
    const colors = (vision.dominantColors || ['warm tones']).join(', ');
    const lighting = vision.lighting || 'natural lighting';
    const setting = vision.settingDetails || 'casual everyday setting';
    const emotion = vision.emotionalTone || 'relatable';
    const cameraMovements = (vision.suggestedCameraMovements || ['slow zoom in']).join(', ');
    
    // Build a concise video prompt optimized for Sora
    // Sora works best with clear, descriptive scene prompts rather than screenplay-style scripts
    const videoScript = [
      `UGC-style smartphone video of ${audience} authentically using ${product} in ${setting}.`,
      description ? `The product is ${description}.` : '',
      `Visual style: ${mood} mood, ${colors} color palette, ${lighting}.`,
      `Camera: ${cameraMovements}, handheld smartphone feel.`,
      `The person picks up ${product} with genuine curiosity, uses it naturally, and reacts with authentic satisfaction.`,
      `Emotional tone: ${emotion}, relatable, candid.`,
      'Natural ambient lighting, shallow depth of field, no text overlays or logos.'
    ].filter(Boolean).join(' ');
    
    return [{
      json: {
        ...item,
        videoScript: videoScript
      }
    }];
  4. Generate video with Sora and poll asynchronously

    First Wait 60 seconds for rate limiting, then HTTP POST to https://api.openai.com/v1/videos (model: sora-2, seconds: 8, size: 720x1280). Sora returns a job ID, not a direct URL. A Code node extracts the videoId, then enters a polling loop: Wait 30 seconds → GET /v1/videos/{videoId} → Check Sora Status1 evaluates isDone (status === 'completed'). Maximum 40 polls (~20 minute timeout). Once complete, Fetch Sora Video Content1 retrieves the video binary.

    Execution prompt · Source text。n8n HTTP Request node Sora Generate Video1 JSON Body field

    ={{ JSON.stringify({
      model: "sora-2",
      prompt: $json.videoScript,
      seconds: '8',
      size: "720x1280"
    }) }}

    Execution prompt · Source text。n8n Code node Extract Sora Job ID1 JS Code field

    const raw = $input.first().json;
    const prevData = $('Video Script Builder1').first().json;
    const response = Array.isArray(raw) ? raw[0] : raw;
    
    if (!response || !response.id) {
      throw new Error('Could not extract video job ID from Sora response: ' + JSON.stringify(raw).substring(0, 500));
    }
    
    return [{
      json: {
        productName: prevData.productName,
        description: prevData.description,
        targetAudience: prevData.targetAudience,
        sheetRowNumber: prevData.sheetRowNumber,
        imageUrl: prevData.imageUrl,
        videoId: response.id,
        videoStatus: response.status,
        progress: response.progress || 0,
        pollCount: 0
      }
    }];

    Execution prompt · Source text。n8n Code node Check Sora Status1 JS Code field

    const raw = $input.first().json;
    const response = Array.isArray(raw) ? raw[0] : raw;
    
    // Get carried data from the job ID node
    const carried = $('Extract Sora Job ID1').first().json;
    
    const status = response.status || '';
    const progress = response.progress || 0;
    const pollCount = (carried.pollCount || 0) + 1;
    const maxPolls = 40; // 20 minutes max
    
    if (response.error) {
      throw new Error('Sora video generation failed: ' + JSON.stringify(response.error));
    }
    
    if (pollCount >= maxPolls) {
      throw new Error('Sora video generation timed out after ' + maxPolls + ' polls (20 minutes)');
    }
    
    return [{
      json: {
        productName: carried.productName,
        description: carried.description,
        targetAudience: carried.targetAudience,
        sheetRowNumber: carried.sheetRowNumber,
        imageUrl: carried.imageUrl,
        videoId: carried.videoId,
        videoStatus: status,
        progress: progress,
        pollCount: pollCount,
        isDone: status === 'completed'
      }
    }];
  5. Upload video to Google Drive and update sheet

    Fetch Sora Video Content1 retrieves the MP4 binary, a Google Drive node uploads it to a specified folder, then an HTTP POST sets public access (anyone/reader). A Code node (Build Public Drive URL1) constructs the shareable link. Finally, a Google Sheets node updates the matching row_number with Status=Done, Image URL, Video URL, and other fields. Loop Over Items proceeds to the next row.

    Execution prompt · Source text。n8n Code node Build Public Drive URL1 JS Code field

    const driveFile = $('Upload Video to Google Drive1').first().json;
    const carried = $('Check Sora Status1').first().json;
    
    const fileId = driveFile.id;
    // Direct shareable link format
    const videoUrl = `https://drive.google.com/file/d/${fileId}/view?usp=sharing`;
    
    return [{
      json: {
        productName: carried.productName,
        description: carried.description,
        targetAudience: carried.targetAudience,
        sheetRowNumber: carried.sheetRowNumber,
        imageUrl: carried.imageUrl,
        videoUrl: videoUrl,
        videoId: carried.videoId,
        driveFileId: fileId
      }
    }];
  6. Configure error handler workflow

    When the main workflow fails, an Error Trigger starts the error handler: Parse Error Details extracts the sheetRowNumber and error message from execution data; if the row is known, it updates Google Sheets with Status=Error and the error message; otherwise it logs only. This workflow must be set as the Error Workflow in the main workflow settings.

    Setup guidance · Source text。n8n error handler workflow Sticky Note; bind as Error Workflow in main workflow settings

    ## ERROR HANDLER WORKFLOW
    This workflow is triggered when the main UGC pipeline fails.
    It extracts the error details and row number, then updates
    the Google Sheet with Status = "Error" and the error message.
    
    SETUP: In the main workflow settings, set this workflow
    as the "Error Workflow".

Prompt you can copy

Setup guidance · Editorial suggestion。Hand to setup assistant; confirm inputs and permissions first

This block is the prompt. Ads are never placed here.

You are an n8n workflow setup assistant. The user wants to replicate the "Generate UGC videos from Google Sheets" template. Assist in this order: 1) Confirm the user has an n8n instance, Google Sheets/Drive OAuth credentials, and an OpenAI API key; 2) Guide importing workflow.json and replacing hardcoded documentId and folderId; 3) Explain each Code node's input variables and output shape; 4) Suggest testing with a single Pending row first, verifying the DALL·E → Vision → Sora chain returns expected fields; 5) Warn about Sora access restrictions and usage-based billing. Do not execute API calls on the user's behalf.

Requires n8n instance + Google OAuth + OpenAI API Key; hardcoded Sheet ID and Drive Folder ID must be replaced.

Source

Generate UGC product videos from Google Sheets with DALL·E, GPT-4 and Sora · vonnairone · n8n.io · 2026-03-23

Comments & feedback

Tried this method? Share results, problems or improvements. Comments appear after moderation. Please avoid sensitive information.

Loading…

    Related