AutomationFlowsAI & RAG › Create Seo-optimised Content From Google Sheets Keywords with Dataforseo and…

Create Seo-optimised Content From Google Sheets Keywords with Dataforseo and…

Original n8n title: Create Seo-optimised Content From Google Sheets Keywords with Dataforseo and Ollama

ByShivang @quietus on n8n.io

This workflow creates content for your primary keywords in a Google Sheet by using SERP data and natural language skills of LLMs to create content at scale.

Event trigger★★★★☆ complexityAI-powered17 nodesMemory Mongo Db ChatOllama ChatGoogle SheetsHTTP RequestAgent
AI & RAG Trigger: Event Nodes: 17 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #14999 — we link there as the canonical source.

This workflow follows the Agent → 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
{
  "id": "gwf7raSYX6GmcQRy",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "SEO-Optimised Keyword-Based Content Workflow",
  "tags": [],
  "nodes": [
    {
      "id": "193aefa0-a8d9-42d5-b0c4-1304c3cfe1eb",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -528,
        -1536
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "b09b15cf-01b5-4afb-8e97-2acb9b9c7ea3",
      "name": "MongoDB Chat Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryMongoDbChat",
      "position": [
        1184,
        -1648
      ],
      "parameters": {
        "sessionKey": "=test_session-1",
        "databaseName": "n8n",
        "sessionIdType": "customKey",
        "collectionName": "n8n"
      },
      "credentials": {
        "mongoDb": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9ccd7bc3-e9a4-4a81-a1bf-4c0abad4be7c",
      "name": "Ollama Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "position": [
        1056,
        -1648
      ],
      "parameters": {
        "model": "llama3.2:latest",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "54360023-15b6-4910-9c4d-fe8e5abe7906",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        144,
        -1536
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "eae90d8a-91b1-4fff-bf3a-180e2786b978",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1120,
        -2192
      ],
      "parameters": {
        "width": 464,
        "height": 896,
        "content": "## Keyword-based Content Writer\n## Keyword-based Content Writer\nThis workflow creates content for your primary keywords in a Google Sheet by using SERP data and natural language skills of LLMs to create content at scale.\n\n\nSay, you have a programmatic SEO Strategy in mind with about 1000 unique keywords. You need to create content for each of them and take live, but you are cautious about not breaking the helpful content policy. \n\nHow do you create helpful content at scale quickly?\n\n1) This workflow fetches the top 10 results, AI Overviews, related searches, and People Also Ask questions for each of your keywords and compiles them in a nice prompt.\n\n2) LLMs have strict rules in place to ensure tone, readability, language, SEO best practices and work limit, which can serve as guidelines for you to customise with more levers.\n\n3) It updates all the content in the sheet. All you need to do is maintain the headers.\n\n4) It can create content for thousands of pages without spending any money by using locally run LLMs and free 5 USD credits from DataforSEO in corporate accounts. \n\n\n**Use Cases**\n\n1) pSEO Pages creation\n\n2) Blog writing based on SERP Data at scale for short-form content\n\n3) Write SEO Optimised content for product and brand pages based on SERP Sentiment.\n\nPre-Requisites:\n\nGoogle Sheets OAuth\nDataForSEO account\nOllama installed locally\nMongoDB connection\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "8e754178-bbf0-4ce2-8672-d77f3b3c85bd",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -352,
        -1344
      ],
      "parameters": {
        "color": 3,
        "width": 640,
        "height": 368,
        "content": "## Step 1: Adding your Google Sheet \n**Create a Google Sheet with the following column headers \n\n'Keyword' - Column with keywords\n\n'Output' - Column where you want your content to be added**\n\nNodes:\n\nThe 'Get row(s) in sheet' Node requires you to connect your Google Account Credentials (Oauth credentials or service accounts) with Google Sheet API & Google Drive API\n\nThe Filter Node keeps the rows where the output is empty.\n\nLoop Over Items Node to loop over each row in the sheet\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "59c89753-6739-45ba-976b-ece2648480f7",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        224,
        -2320
      ],
      "parameters": {
        "color": 6,
        "width": 704,
        "height": 432,
        "content": "## Step 2: Fetching SERP results\n**Using the DataforSEO API, this node captures the following SERP results data:\n\n1. Top Search Results\n2. AI Overviews\n3. People Also Ask (PAA)\n4. Related Searches**\n\n\nNodes:\n\nHTTP Request Node:\n1) This node calls the DataforSEO API to fetch the data. Create an account using your work email to get 5 USD in Credits, which are more than enough to start making hundreds of requests.\nCheck out: https://dataforseo.com/blog/a-kickstart-guide-to-using-dataforseo-apis\n\n2) Code node cleans the output and compiles it into text for the prompt.\n\n3) If Node checks if the HTTP Request Status Code is 200, and throws an error in the Google Sheet if not."
      },
      "typeVersion": 1
    },
    {
      "id": "3e547a07-5763-49a7-b559-47702d0901ec",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1024,
        -1472
      ],
      "parameters": {
        "width": 944,
        "height": 496,
        "content": "## Step 3: Setting up AI Agent \n**AI Agent has 2 major parts, AI Model to run and Storage**\n\nAI Model: I recommend using a local LLM model like Ollama 3.2 to save money on API tokens, which is good at creating content and can run well with an M1 Chip & 16GB RAM. Check out: https://docs.ollama.com/quickstart\n\nDatabase: I have used MongoDB as storage. It is easy to configure and has a generous free tier. Use the Connection String Method to add your account.\n\nCheck out: https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.mongodb/\n\n\nNodes:\n\nAI Agent Node to add a prompt and create content.\n\nCode Node to clean the output in HTML format. You can change the final format as you want. This one is HTML.\n\nUpdate Row in Sheet Node to add the final content in the respective row. Make sure to check that the matching column matches the Keyword, and the Update column should be the same as the Output Column Header.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "9219e6ae-fdd1-46a3-b911-9d90c4f1d57c",
      "name": "Update row in sheet1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        816,
        -1680
      ],
      "parameters": {
        "columns": {
          "value": {
            "HTML": "HTTP Node Failed to fetch results",
            "Keyword": "={{ $('Fetch Rows from your Google Sheet').item.json.Keyword }}"
          },
          "schema": [
            {
              "id": "Keyword",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Intent",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Intent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Volume",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Volume",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Keyword Difficulty",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Keyword Difficulty",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "HTML",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "HTML",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "SERP Features",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "SERP Features",
              "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": [
            "Keyword"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 799174481,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nJxVcRVw2Nk8eJInLm-Ek4u3f8kzS5FVBnCHYB4A9H4/edit#gid=799174481",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1nJxVcRVw2Nk8eJInLm-Ek4u3f8kzS5FVBnCHYB4A9H4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nJxVcRVw2Nk8eJInLm-Ek4u3f8kzS5FVBnCHYB4A9H4/edit?usp=drivesdk",
          "cachedResultName": "Test-1"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "bd089cfa-81e5-46ed-b152-e2bd607e02c6",
      "name": "Fetch Rows from your Google Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -304,
        -1536
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 799174481,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nJxVcRVw2Nk8eJInLm-Ek4u3f8kzS5FVBnCHYB4A9H4/edit#gid=799174481",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1nJxVcRVw2Nk8eJInLm-Ek4u3f8kzS5FVBnCHYB4A9H4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nJxVcRVw2Nk8eJInLm-Ek4u3f8kzS5FVBnCHYB4A9H4/edit?usp=drivesdk",
          "cachedResultName": "Test-1"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": false
    },
    {
      "id": "890bfefb-b32b-43ec-b5b5-fc5e650acec4",
      "name": "Filter rows that are already populated",
      "type": "n8n-nodes-base.filter",
      "position": [
        -80,
        -1536
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "4fc29579-efc1-4ad8-a3b6-c783b8837abb",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.HTML }}",
              "rightValue": "="
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "7862c992-7160-414e-9c38-b431c8ddaf77",
      "name": "Fetch SERP Results + AI Mode Answers",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        368,
        -1776
      ],
      "parameters": {
        "url": "https://api.dataforseo.com/v3/serp/google/organic/live/advanced",
        "method": "POST",
        "options": {},
        "jsonBody": "=[\n  {\n    \"keyword\": \"{{ $json.Keyword }}\",\n    \"location_code\": 2356,\n    \"language_code\": \"en\",\n    \"device\": \"desktop\",\n    \"os\": \"windows\"\n  }\n]",
        "sendBody": true,
        "sendQuery": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "queryParameters": {
          "parameters": []
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "5bd571b8-3c04-4e8c-a507-1caa53a65c41",
      "name": "Error Handling",
      "type": "n8n-nodes-base.if",
      "position": [
        592,
        -1776
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a5178e3d-4365-47b9-9917-06196a28c67c",
              "operator": {
                "type": "number",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status_code }}",
              "rightValue": 20000
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "28631d02-507c-40d5-91f6-0a435a77270e",
      "name": "Compile SERP Data into one Output for Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        816,
        -1872
      ],
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst result = data.tasks?.[0]?.result?.[0];\nconst items = result?.items || [];\n\nlet compiled = [];\n\n// \u2500\u2500 1. AI Overview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst aiOverview = items.find(item => item.type === 'ai_overview');\n\nif (aiOverview) {\n  compiled.push('=== AI OVERVIEW ===');\n  (aiOverview.items || []).forEach(block => {\n    if (block.text && block.type === 'ai_overview_element') {\n      // Clean the text - remove image references and video descriptions\n      const cleanText = block.text\n        .replace(/\\!\\[.*?\\]\\(.*?\\)/g, '')  // remove markdown images\n        .replace(/\\d+s\\s+.*?YouTube.*?\\d{4}/g, '')  // remove video snippets\n        .trim();\n      if (cleanText) {\n        if (block.title) compiled.push(`${block.title}`);\n        compiled.push(cleanText);\n      }\n    }\n  });\n}\n\n// \u2500\u2500 2. Organic Results \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst organic = items.filter(item => item.type === 'organic');\nif (organic.length > 0) {\n  compiled.push('\\n=== TOP SEARCH RESULTS ===');\n  organic.slice(0, 5).forEach((r, i) => {\n    compiled.push(`[${i + 1}] ${r.title || ''}`);\n    compiled.push(`URL: ${r.url || ''}`);\n    compiled.push(`${r.description || ''}`);\n    compiled.push('');\n  });\n}\n\n// \u2500\u2500 3. People Also Ask \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst paa = items.find(item => item.type === 'people_also_ask');\nif (paa) {\n  compiled.push('\\n=== RELATED QUESTIONS ===');\n  (paa.items || []).forEach(q => {\n    const answer = q.expanded_element?.[0]?.description || '';\n    if (answer) {\n      compiled.push(`Q: ${q.title || ''}`);\n      compiled.push(`A: ${answer}`);\n      compiled.push('');\n    }\n  });\n}\n\n// \u2500\u2500 4. Related Searches \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst relatedSearches = items.find(item => item.type === 'related_searches');\nif (relatedSearches?.items?.length > 0) {\n  compiled.push('\\n=== RELATED SEARCHES ===');\n  compiled.push(relatedSearches.items.join(', '));\n}\n\nconst finalText = compiled\n  .filter(l => l !== null && l !== undefined)\n  .join('\\n')\n  .trim();\n\nreturn [{ json: { compiled_text: finalText || 'No data available' } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "9af2ded9-5a35-4e07-8af1-a4a7e25a65ac",
      "name": "Compose SEO-optimised Content",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1040,
        -1872
      ],
      "parameters": {
        "text": "=Using the {{ $('Loop Over Items').item.json.Keyword }},{{ $json.compiled_text }} , write 200 words on the topic and compile your findings in a blog format with no more than 500 words. Follow the following blog guidelines:\n\n1) keep the language simple but casual. \n\n2) write in humansied format.\n\n3) add a table with locations.\n\n4) optimise for the keyword for good SEO.\n\n5) no keyword stuffing.\n\n6) give the output in html formatting.\n\n7) stick to the topic and search like how a user would search on google.\n\n8) title should  be in the format \"Where was {movie name} shot?\"\n\n9) add FAQs to optimise for SEO.\n\n10) follow a typical blog format with introduction, body and conclusion\n\n11) Return ONLY raw HTML. Do NOT use any JSON wrapper, function call format, \ntool call format, or code fences. Start your response directly with <!DOCTYPE html>\n",
        "options": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "581366b9-eafe-41ab-ba5c-049e6847a871",
      "name": "Clean output",
      "type": "n8n-nodes-base.code",
      "position": [
        1392,
        -1872
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json.output;\n\nconst cleaned = raw.split('\\\\n').join('');\n\nreturn [{ json: { output: cleaned } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "36f67d9c-5324-4bcf-b3d0-66b1bab0ee99",
      "name": "Update Row with HTML Output",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1616,
        -1712
      ],
      "parameters": {
        "columns": {
          "value": {
            "HTML": "={{ $json.compiled_text }}",
            "Keyword": "={{ $('Fetch Rows from your Google Sheet').item.json.Keyword }}"
          },
          "schema": [
            {
              "id": "Keyword",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Keyword",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Intent",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Intent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Volume",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Volume",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Keyword Difficulty",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Keyword Difficulty",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "HTML",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "HTML",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "SERP Features",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "SERP Features",
              "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": [
            "Keyword"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 799174481,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nJxVcRVw2Nk8eJInLm-Ek4u3f8kzS5FVBnCHYB4A9H4/edit#gid=799174481",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1nJxVcRVw2Nk8eJInLm-Ek4u3f8kzS5FVBnCHYB4A9H4",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nJxVcRVw2Nk8eJInLm-Ek4u3f8kzS5FVBnCHYB4A9H4/edit?usp=drivesdk",
          "cachedResultName": "Test-1"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "8a9eece9-b2bc-405e-9197-cbfeb79c3fe4",
  "connections": {
    "Clean output": {
      "main": [
        [
          {
            "node": "Update Row with HTML Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Handling": {
      "main": [
        [
          {
            "node": "Compile SERP Data into one Output for Prompt",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update row in sheet1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Fetch SERP Results + AI Mode Answers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ollama Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Compose SEO-optimised Content",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "MongoDB Chat Memory": {
      "ai_memory": [
        [
          {
            "node": "Compose SEO-optimised Content",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Update row in sheet1": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Row with HTML Output": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compose SEO-optimised Content": {
      "main": [
        [
          {
            "node": "Clean output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Rows from your Google Sheet": {
      "main": [
        [
          {
            "node": "Filter rows that are already populated",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch SERP Results + AI Mode Answers": {
      "main": [
        [
          {
            "node": "Error Handling",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Fetch Rows from your Google Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter rows that are already populated": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compile SERP Data into one Output for Prompt": {
      "main": [
        [
          {
            "node": "Compose SEO-optimised Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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

This workflow creates content for your primary keywords in a Google Sheet by using SERP data and natural language skills of LLMs to create content at scale.

Source: https://n8n.io/workflows/14999/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This workflow creates a multi-talented AI assistant named Simran that interacts with users via Telegram. It can handle text and voice messages, understand the user's intent, and perform various tasks.

MongoDB, Chain Llm, Google Gemini Chat +11
AI & RAG

This workflow is designed for marketers, content creators, agencies, and solo founders who want to publish long‑form posts with visuals on autopilot using n8n and AI agents. ​

Tool Http Request, Agent, HTTP Request +27
AI & RAG

Description

Agent, Edit Image, HTTP Request +6
AI & RAG

This automated n8n workflow streamlines real estate marketing by combining voice campaigns and email outreach with AI-powered lead generation. The system monitors real estate offers, generates persona

Ollama Chat, Google Sheets Trigger, Google Sheets +3
AI & RAG

AI Stock Analysis Automation (EODHD + Google Sheets + AI). Uses googleSheets, httpRequest, agent, lmChatOpenAi. Event-driven trigger; 20 nodes.

Google Sheets, HTTP Request, Agent +2