AutomationFlowsData & Sheets › Clean Optimized Workflow

Clean Optimized Workflow

Clean Optimized Workflow. Uses googleSheets, chainLlm, lmChatGroq, outputParserStructured. Event-driven trigger; 24 nodes.

Event trigger★★★★☆ complexityAI-powered24 nodesGoogle SheetsChain LlmGroq ChatOutput Parser StructuredHTTP Request
Data & Sheets Trigger: Event Nodes: 24 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Chainllm → Google Sheets 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
{
  "name": "Clean Optimized Workflow",
  "nodes": [
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        400,
        300
      ],
      "id": "manual-trigger",
      "name": "Manual Trigger"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "1kh0f3fhpCSvlv0jQrELaVHHSAByQikVLc08zwp7YBWM",
          "mode": "list"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "status",
              "lookupValue": "new"
            }
          ]
        },
        "options": {
          "returnFirstMatch": true
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.6,
      "position": [
        600,
        300
      ],
      "id": "get-row",
      "name": "Get Pending Row",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.idea }}",
        "hasOutputParser": true,
        "messages": {
          "messageValues": [
            {
              "message": "You are a professional motivational content creator. Your task is to generate a powerful, cinematic-style motivational script designed for a short-form video under 60 seconds. The final output should be split into 3 parts, each tied to a specific black & white image and audio segment.\n\nRequirements:\n- Each image must depict **only one person** in a wide or mid-shot (no close-up shots).\n- The image prompt should be **detailed**, cinematic, and emotionally expressive.\n- Every image description must end with: **\"Black & White\"**\n- Each part should include:\n  1. **Image Prompt** \u2013 A highly visual, detailed prompt for generating a black & white image.\n  2. **Script Text** \u2013 A motivational sentence (1\u20132 lines) that can be read in 5\u20137 seconds.\n\nConstraints:\n- Total combined audio must be under 60 seconds.\n- Keep language grounded, simple, and impactful.\n- No hashtags, bullet points, or emojis.\n- Topic: {{ $json.idea }}\n\nOutput Format (JSON):\n{\n  \"image_1\": \"....\",\n  \"image_2\": \"....\",\n  \"image_3\": \"....\",\n  \"audio_1\": \"....\",\n  \"audio_2\": \"....\",\n  \"audio_3\": \"....\"\n}"
            }
          ]
        }
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        800,
        300
      ],
      "id": "generate-script",
      "name": "Generate Script"
    },
    {
      "parameters": {
        "model": "llama-3.3-70b-versatile",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "typeVersion": 1,
      "position": [
        800,
        460
      ],
      "id": "groq",
      "name": "Groq Model",
      "credentials": {
        "groqApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsonSchemaExample": "{\n  \"image_1\": \"...\",\n  \"image_2\": \"...\",\n  \"image_3\": \"...\",\n  \"audio_1\": \"...\",\n  \"audio_2\": \"...\",\n  \"audio_3\": \"...\"\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        800,
        620
      ],
      "id": "parser",
      "name": "Output Parser"
    },
    {
      "parameters": {
        "jsCode": "// Simple hash function\nfunction simpleHash(str) {\n  let hash = 0;\n  for (let i = 0; i < str.length; i++) {\n    const char = str.charCodeAt(i);\n    hash = ((hash << 5) - hash) + char;\n    hash = hash & hash;\n  }\n  return Math.abs(hash).toString(36);\n}\n\nconst output = $input.first().json.output;\nconst items = [];\n\n// Process images\nfor (let i = 1; i <= 3; i++) {\n  const script = output[`image_${i}`];\n  if (script) {\n    items.push({\n      json: {\n        type: 'image',\n        index: i,\n        script: script,\n        script_hash: simpleHash(script)\n      }\n    });\n  }\n}\n\n// Process audio\nfor (let i = 1; i <= 3; i++) {\n  const script = output[`audio_${i}`];\n  if (script) {\n    items.push({\n      json: {\n        type: 'audio',\n        index: i,\n        script: script,\n        script_hash: simpleHash(script)\n      }\n    });\n  }\n}\n\nreturn items;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1000,
        300
      ],
      "id": "split-hash",
      "name": "Split & Hash"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "1kh0f3fhpCSvlv0jQrELaVHHSAByQikVLc08zwp7YBWM",
          "mode": "list"
        },
        "sheetName": {
          "__rl": true,
          "value": 1778583878,
          "mode": "list",
          "cachedResultName": "Generated_Assets"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "script_hash",
              "lookupValue": "={{ $json.script_hash }}"
            },
            {
              "lookupColumn": "type",
              "lookupValue": "={{ $json.type }}"
            }
          ]
        },
        "options": {
          "returnFirstMatch": true
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.6,
      "position": [
        1200,
        300
      ],
      "id": "check-cache",
      "name": "Check Cache",
      "continueOnFail": true,
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Merge original item with cache result\nconst originalItem = $('Split & Hash').item.json;\nconst cacheResult = $input.first().json;\n\n// Check if we found a cached asset\nconst cachedUrl = cacheResult.asset_url || null;\n\nreturn [{\n  json: {\n    ...originalItem,\n    cached_url: cachedUrl,\n    needs_generation: cachedUrl === null\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1400,
        300
      ],
      "id": "merge-cache-result",
      "name": "Merge Cache Result"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "needs-gen",
              "leftValue": "={{ $json.needs_generation }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1600,
        300
      ],
      "id": "needs-gen",
      "name": "Needs Generation?"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "asset_url",
              "value": "={{ $json.cached_url }}",
              "type": "string"
            },
            {
              "id": "2",
              "name": "type",
              "value": "={{ $json.type }}",
              "type": "string"
            },
            {
              "id": "3",
              "name": "index",
              "value": "={{ $json.index }}",
              "type": "number"
            },
            {
              "id": "4",
              "name": "script_hash",
              "value": "={{ $json.script_hash }}",
              "type": "string"
            },
            {
              "id": "5",
              "name": "from_cache",
              "value": true,
              "type": "boolean"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1800,
        200
      ],
      "id": "use-cached",
      "name": "Use Cached"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "is-image",
              "leftValue": "={{ $json.type }}",
              "rightValue": "image",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1800,
        400
      ],
      "id": "type-check",
      "name": "Type?"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://queue.fal.run/fal-ai/flux-pro/v1.1-ultra",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"prompt\": \"{{ $json.script }}\",\n  \"num_images\": 1,\n  \"aspect_ratio\": \"9:16\"\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2000,
        320
      ],
      "id": "gen-image",
      "name": "Start Image Gen",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://queue.fal.run/fal-ai/elevenlabs/tts/multilingual-v2",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": \"{{ $json.script }}\",\n  \"voice\": \"Brian\",\n  \"stability\": 0.5,\n  \"similarity_boost\": 0.75,\n  \"speed\": 1\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2000,
        480
      ],
      "id": "gen-audio",
      "name": "Start Audio Gen",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "// Poll for image completion\nconst requestId = $input.first().json.request_id;\nconst originalItem = $('Type?').item.json;\nconst maxRetries = 40;\nconst delay = 5000;\n\nfor (let i = 0; i < maxRetries; i++) {\n  const result = await this.helpers.httpRequestWithAuthentication.call(\n    this,\n    'httpHeaderAuth',\n    {\n      method: 'GET',\n      url: `https://queue.fal.run/fal-ai/flux-pro/requests/${requestId}`\n    }\n  );\n\n  if (result.status === 'COMPLETED') {\n    return [{\n      json: {\n        asset_url: result.images[0].url,\n        type: 'image',\n        index: originalItem.index,\n        script_hash: originalItem.script_hash,\n        from_cache: false\n      }\n    }];\n  }\n\n  if (result.status === 'FAILED') {\n    throw new Error(`Image generation failed: ${JSON.stringify(result.error)}`);\n  }\n\n  await new Promise(resolve => setTimeout(resolve, delay));\n}\n\nthrow new Error('Image generation timeout');"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        320
      ],
      "id": "poll-image",
      "name": "Poll Image",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 10000
    },
    {
      "parameters": {
        "jsCode": "// Poll for audio completion\nconst requestId = $input.first().json.request_id;\nconst originalItem = $('Type?').item.json;\nconst maxRetries = 40;\nconst delay = 5000;\n\nfor (let i = 0; i < maxRetries; i++) {\n  const result = await this.helpers.httpRequestWithAuthentication.call(\n    this,\n    'httpHeaderAuth',\n    {\n      method: 'GET',\n      url: `https://queue.fal.run/fal-ai/elevenlabs/requests/${requestId}`\n    }\n  );\n\n  if (result.status === 'COMPLETED') {\n    return [{\n      json: {\n        asset_url: result.audio.url,\n        type: 'audio',\n        index: originalItem.index,\n        script_hash: originalItem.script_hash,\n        from_cache: false\n      }\n    }];\n  }\n\n  if (result.status === 'FAILED') {\n    throw new Error(`Audio generation failed: ${JSON.stringify(result.error)}`);\n  }\n\n  await new Promise(resolve => setTimeout(resolve, delay));\n}\n\nthrow new Error('Audio generation timeout');"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        480
      ],
      "id": "poll-audio",
      "name": "Poll Audio",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 10000
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "1kh0f3fhpCSvlv0jQrELaVHHSAByQikVLc08zwp7YBWM",
          "mode": "list"
        },
        "sheetName": {
          "__rl": true,
          "value": 1778583878,
          "mode": "list",
          "cachedResultName": "Generated_Assets"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "script_hash": "={{ $json.script_hash }}",
            "asset_url": "={{ $json.asset_url }}",
            "type": "={{ $json.type }}",
            "timestamp": "={{ $now.toISO() }}",
            "status": "completed"
          }
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2200,
        400
      ],
      "id": "save-cache",
      "name": "Save To Cache",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll"
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2400,
        300
      ],
      "id": "merge-all",
      "name": "Merge All"
    },
    {
      "parameters": {
        "jsCode": "// Rebuild final structure\nconst items = $input.all();\nconst result = { json: {} };\n\nitems.forEach(item => {\n  const key = `${item.json.type}_${item.json.index}`;\n  result.json[key] = item.json.asset_url;\n});\n\n// Add stats\nconst cached = items.filter(i => i.json.from_cache).length;\nresult.json.stats = {\n  total: items.length,\n  cached: cached,\n  generated: items.length - cached\n};\n\nreturn [result];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2600,
        300
      ],
      "id": "rebuild",
      "name": "Rebuild Structure"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "Image 1",
              "value": "={{ $json.image_1 }}",
              "type": "string"
            },
            {
              "id": "2",
              "name": "Audio 1",
              "value": "={{ $json.audio_1 }}",
              "type": "string"
            },
            {
              "id": "3",
              "name": "Image 2",
              "value": "={{ $json.image_2 }}",
              "type": "string"
            },
            {
              "id": "4",
              "name": "Audio 2",
              "value": "={{ $json.audio_2 }}",
              "type": "string"
            },
            {
              "id": "5",
              "name": "Image 3",
              "value": "={{ $json.image_3 }}",
              "type": "string"
            },
            {
              "id": "6",
              "name": "Audio 3",
              "value": "={{ $json.audio_3 }}",
              "type": "string"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2800,
        300
      ],
      "id": "final-output",
      "name": "Final Output"
    },
    {
      "parameters": {
        "content": "## START\nGet pending row",
        "height": 200,
        "width": 300,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        380,
        200
      ],
      "id": "note1",
      "name": "Note 1"
    },
    {
      "parameters": {
        "content": "## SCRIPT GENERATION\nCreate motivational content",
        "height": 400,
        "width": 300,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        780,
        200
      ],
      "id": "note2",
      "name": "Note 2"
    },
    {
      "parameters": {
        "content": "## SPLIT & CACHE CHECK\nFor each item:\n1. Hash the script\n2. Check if cached\n3. Route based on cache",
        "height": 200,
        "width": 400,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        980,
        200
      ],
      "id": "note3",
      "name": "Note 3"
    },
    {
      "parameters": {
        "content": "## GENERATION\nGenerate only if not cached\nPoll until ready\nSave to cache",
        "height": 360,
        "width": 680,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1560,
        240
      ],
      "id": "note4",
      "name": "Note 4"
    },
    {
      "parameters": {
        "content": "## MERGE & OUTPUT\nCombine all assets\nPrepare for video processing",
        "height": 200,
        "width": 480,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2360,
        200
      ],
      "id": "note5",
      "name": "Note 5"
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Get Pending Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Pending Row": {
      "main": [
        [
          {
            "node": "Generate Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Script": {
      "main": [
        [
          {
            "node": "Split & Hash",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Groq Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Script",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Generate Script",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Split & Hash": {
      "main": [
        [
          {
            "node": "Check Cache",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Cache": {
      "main": [
        [
          {
            "node": "Merge Cache Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Cache Result": {
      "main": [
        [
          {
            "node": "Needs Generation?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Generation?": {
      "main": [
        [
          {
            "node": "Type?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Use Cached",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Use Cached": {
      "main": [
        [
          {
            "node": "Merge All",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Type?": {
      "main": [
        [
          {
            "node": "Start Image Gen",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Start Audio Gen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Image Gen": {
      "main": [
        [
          {
            "node": "Poll Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Audio Gen": {
      "main": [
        [
          {
            "node": "Poll Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Image": {
      "main": [
        [
          {
            "node": "Save To Cache",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Audio": {
      "main": [
        [
          {
            "node": "Save To Cache",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save To Cache": {
      "main": [
        [
          {
            "node": "Merge All",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge All": {
      "main": [
        [
          {
            "node": "Rebuild Structure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rebuild Structure": {
      "main": [
        [
          {
            "node": "Final Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "id": "CleanOptimizedWorkflow",
  "tags": []
}

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

Clean Optimized Workflow. Uses googleSheets, chainLlm, lmChatGroq, outputParserStructured. Event-driven trigger; 24 nodes.

Source: https://gist.github.com/CodeOne45/3f5bea37b1a57a0460955193952fc10b — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

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

Data & Sheets

Smart-Table-Fill.N8N. Uses httpRequest, googleSheets, chainLlm, outputParserStructured. Event-driven trigger; 31 nodes.

HTTP Request, Google Sheets, Chain Llm +3
Data & Sheets

Smart-Folder2Table. Uses executeWorkflowTrigger, httpRequest, chainLlm, lmChatGroq. Event-driven trigger; 26 nodes.

Execute Workflow Trigger, HTTP Request, Chain Llm +4
Data & Sheets

This workflow demonstrates a simple way to run evals on a set of test cases stored in a Google Sheet.

Google Sheets, Output Parser Structured, Chain Llm +3
Data & Sheets

Splitout Code. Uses lmChatOpenRouter, outputParserStructured, httpRequest, stickyNote. Event-driven trigger; 11 nodes.

OpenRouter Chat, Output Parser Structured, HTTP Request +2
Data & Sheets

This workflow automates web scraping of Amazon search result pages by retrieving raw HTML, cleaning it to retain only the relevant product elements, and then using an LLM to extract structured product

OpenRouter Chat, Output Parser Structured, HTTP Request +2