AutomationFlowsAI & RAG › Linkedin Content Automation: AI Post Creation & Images with Sheet Approval…

Linkedin Content Automation: AI Post Creation & Images with Sheet Approval…

Original n8n title: Linkedin Content Automation: AI Post Creation & Images with Sheet Approval Workflow

ByOptimum Office Solution @isight on n8n.io

This workflow is designed for marketers, content creators, agency owners, and solopreneurs who want to automate LinkedIn content creation using AI. It helps turn Google Sheets entries into complete LinkedIn posts, including text, image prompts, and AI-generated images.

Event trigger★★★★☆ complexityAI-powered18 nodesGoogle Sheets Trigger@Tavily/N8N Nodes TavilyGoogle SheetsAgentOllama ToolOllama ChatGmailOllama
AI & RAG Trigger: Event Nodes: 18 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Agent → Gmail 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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "bce5b25b-7ce9-42e2-9488-6bb607600663",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4752,
        -768
      ],
      "parameters": {
        "width": 576,
        "height": 912,
        "content": "## Who\u2019s it for\nThis workflow is designed for marketers, content creators, agency owners, and solopreneurs who want to automate LinkedIn content creation using AI. It helps turn Google Sheets entries into complete LinkedIn posts, including text, image prompts, and AI-generated images.\n\n## What it does / How it works\nThe workflow monitors a Google Sheet for new campaign entries. When a row is added, it automatically collects details about the campaign, searches LinkedIn via Tavily to identify relevant trends, and turns the information into an AI-generated LinkedIn post using a local Ollama model or an LLM of your choice.\nA second approval step lets you refine the text directly inside the sheet. Once approved, the workflow generates an image prompt, creates a ready-to-post visual with OpenAI Images, and finally publishes the post to LinkedIn.\n\n## How to set up\n\u2022 Add your own Google Sheets Trigger credentials.\n\u2022 Add Tavily, Ollama/OpenAI, and LinkedIn OAuth credentials.\n\u2022 Replace the sample Sheet URL with your own.\n\u2022 Set your LinkedIn account/person ID in the posting node.\n\n## Requirements\n\u2022 Google Sheets account\n\u2022 LinkedIn OAuth app\n\u2022 Tavily API key\n\u2022 Ollama (local) or OpenAI image generation\n\n##   How to customize\nYou can change:\n\u2022 AI model\n\u2022 Image generation provider\n\u2022 Search query logic\n\u2022 Content tone\n\u2022 Approval step (manual or automatic)"
      },
      "typeVersion": 1
    },
    {
      "id": "4d7edd6f-1ce7-4abb-904e-5bc48d106bc2",
      "name": "Google Sheets Trigger",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "position": [
        -4064,
        -120
      ],
      "parameters": {
        "event": "rowAdded",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultUrl": "",
          "cachedResultName": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": ""
        }
      },
      "credentials": {
        "googleSheetsTriggerOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "62368126-b2d0-4fb2-a09e-b415cf8d25d0",
      "name": "Search",
      "type": "@tavily/n8n-nodes-tavily.tavily",
      "position": [
        -3392,
        -120
      ],
      "parameters": {
        "query": "={{$json[\"Campaign\"] + \" \" + $json[\"Subject\"] + \" for \" + $json[\"Audience\"] + \" site:linkedin.com\"}}\n",
        "options": {}
      },
      "credentials": {
        "tavilyApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "14087093-7fa6-4099-9380-f2cbd9f5110a",
      "name": "chat input",
      "type": "n8n-nodes-base.code",
      "position": [
        -3168,
        -120
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  // 1\ufe0f\u20e3 Google Sheet values\n  const campaign = item.json[\"Campaign\"] || \"N/A\";\n  const subject = item.json[\"Subject\"] || \"N/A\";\n  const audience = item.json[\"Audience\"] || \"N/A\";\n\n  // 2\ufe0f\u20e3 Tavily results\n  let tavilyResults = \"\";\n  if (Array.isArray(item.json.results) && item.json.results.length > 0) {\n    tavilyResults = item.json.results\n      .map((r, i) => {\n        const title = r.title || `Result ${i + 1}`;\n        const content = r.content || r.text || r.snippet || \"\";\n        return `\u2022 ${title}: ${content}`;\n      })\n      .filter(Boolean)\n      .join(\"\\n\");\n  } else {\n    tavilyResults = \"No relevant posts found.\";\n  }\n\n  // 3\ufe0f\u20e3 Create chatInput for AI Agent\n  item.json.chatInput = `\nCampaign: ${campaign}\nSubject: ${subject}\nAudience: ${audience}\n\nTavily search results:\n${tavilyResults}\n\nPlease generate a professional and engaging LinkedIn post for this campaign.\nInclude:\n- Hook (1-2 lines)\n- Key insights (2-3 sentences or bullet points)\n- Call-to-action (CTA)\nMake it ready to publish and include emojis relevant to the campaign, subject, and audience.\n  `.trim();\n\n  return item;\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "b376b798-c12f-4ae5-9c59-ab3e65b8b40c",
      "name": "Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        -3616,
        -120
      ],
      "parameters": {
        "jsCode": "// Process multiple items and return only the desired fields\nreturn items.map(item => {\n  return {\n    json: {\n      Campaign: item.json.Campaign,\n      Subject: item.json.Subject,\n      Audience: item.json.Audience,\n      rowNumber: item.json.ID  // use ID from trigger as row number\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "58630aa1-0693-4501-98a0-d4f4b0b440c1",
      "name": "Add the post to the sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -2592,
        -192
      ],
      "parameters": {
        "columns": {
          "value": {
            "ID": "={{ $('Google Sheets Trigger').item.json.ID }}",
            "LinkedIn_Post ": "={{ $json.output }}"
          },
          "schema": [
            {
              "id": "ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Campaign",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Campaign",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Subject",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Subject",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Audience",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Audience",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LinkedIn_Post ",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "LinkedIn_Post ",
              "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": [
            "ID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1wcZ5YdaLQB_5bD5O6t6hMZAWOBzT79DCrkPJ5Gx0P4s/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": ""
        }
      },
      "executeOnce": false,
      "typeVersion": 4.7
    },
    {
      "id": "aae74c47-6793-4061-b674-668ef0286be6",
      "name": "Filter",
      "type": "n8n-nodes-base.filter",
      "position": [
        -3840,
        -120
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a83b0f61-51fd-4bba-baf3-25765b0485f8",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.LinkedIn_Post }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e4121c82-f4b3-483d-95aa-8196946615c6",
      "name": "Content Generator",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -2944,
        -120
      ],
      "parameters": {
        "text": "=Campaign: {{ $('Google Sheets Trigger').item.json.Campaign }}\nSubject: {{ $('Google Sheets Trigger').item.json.Subject }}\nAudience: {{ $('Google Sheets Trigger').item.json.Audience }}\nTavily search results: {{ $json.chatInput }}\nfeedback: {{ $json.data['If No, write your comments here:'] }}\n\nYour task:\nCreate a professional, engaging, ready-to-publish LinkedIn post based on the above results and reviewer feedback. \nUse the previous draft as a base. Make improvements only where necessary according to the reviewer feedback. \nThe post must:\n1. Start with a hook (1-2 lines)\n2. Include key insights or highlights (2-3 sentences or bullet points)\n3. End with a call-to-action (CTA)\n4. Be concise, polished, and written in a friendly professional tone\n5. Include emojis relevant to the campaign, subject, and audience\n\nDo not summarize in article format. Write as a social media post, ready for LinkedIn publication.\nJust give me the post content, do not include explanations or intros/outros.\n",
        "options": {
          "systemMessage": "You are a LinkedIn content creation agent. Your job is to create professional, polished, and engaging LinkedIn posts for campaigns. When appropriate, include emojis that fit the campaign, subject, and audience.\n\nUse a positive and professional tone suitable for LinkedIn.\n\nEach post must include:\n\nA hook (1\u20132 lines)\n\nKey insights (2\u20133 sentences or bullet points)\n\nA call-to-action (CTA)\n\nIntegrate relevant information from Tavily search results and recent trends. The final output must be clean, ready-to-publish text with no raw JSON, code, or technical artifacts. Use emojis sparingly to highlight key points or make the post more engaging.",
          "returnIntermediateSteps": false
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "12083825-af2f-42f4-8ce8-61bde432ab72",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4120,
        -280
      ],
      "parameters": {
        "color": 7,
        "width": 1104,
        "height": 320,
        "content": "## 1. Trigger & Data Flow\nThe Google Sheets Trigger starts the flow, sending new data to the Filter node. This filters out rows that are not ready for content creation, then Code extracts variables."
      },
      "typeVersion": 1
    },
    {
      "id": "f36b7248-fd34-4ecf-8eaf-fc46ea927009",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2984,
        -328
      ],
      "parameters": {
        "color": 7,
        "width": 976,
        "height": 528,
        "content": "## 2. Content Generation & Approval\nThe Content Generator drafts the post, using the Ollama model. The draft is saved back to the sheet, then sent via Approval Email. The Approved? node checks the response for next steps (Publish or Revise)."
      },
      "typeVersion": 1
    },
    {
      "id": "d82342ad-0ce9-4d4e-a970-c462ac1b8fbd",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1968,
        -304
      ],
      "parameters": {
        "color": 7,
        "width": 960,
        "height": 464,
        "content": "## 3. Image & Publish\nThe approved post is used by Image Prompt to create a detailed prompt. Generate image uses this to generate the visual. The final content and image are then published automatically to LinkedIn using Create a post."
      },
      "typeVersion": 1
    },
    {
      "id": "25424d6a-741f-459a-8aa0-323cc4da46a3",
      "name": "Message a model in Ollama",
      "type": "@n8n/n8n-nodes-langchain.ollamaTool",
      "position": [
        -1776,
        48
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {},
        "messages": {
          "values": [
            {}
          ]
        }
      },
      "credentials": {},
      "typeVersion": 1
    },
    {
      "id": "9fcb0038-d32b-47cf-b9d8-2fc501fddc47",
      "name": "Ollama Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "position": [
        -2944,
        48
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "cc03e3f7-530e-482b-a07a-78b1d045d2a7",
      "name": "Approval Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -2368,
        -192
      ],
      "parameters": {
        "message": "=Hi! I hope you\u2019re doing well.\nPlease take a moment to review the content below and share any feedback you may have.\n\n\"{{ $json['LinkedIn_Post '] }}\"\n\nYou can approve it as is or let me know if you\u2019d like any changes.",
        "options": {},
        "subject": "Approval for LinkedIn Post Content",
        "operation": "sendAndWait",
        "formFields": {
          "values": [
            {
              "fieldType": "checkbox",
              "fieldLabel": "Do you approve this text?",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Yes"
                  },
                  {
                    "option": "No"
                  }
                ]
              }
            },
            {
              "fieldLabel": "If No, write your comments here:"
            }
          ]
        },
        "responseType": "customForm"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "76034183-cb3f-415e-8437-5ebb09b6a1ff",
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "position": [
        -2144,
        -120
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "64f85080-7208-4341-a052-ef4fe16f2c2c",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data['Do you approve this text?'][0] }}",
              "rightValue": "={{\"Yes\"}}"
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "e771d454-3a29-4efa-8946-ffde4511df78",
      "name": "Image Prompt",
      "type": "@n8n/n8n-nodes-langchain.ollama",
      "position": [
        -1920,
        -120
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "llama3.2:latest",
          "cachedResultName": "llama3.2:latest"
        },
        "options": {
          "system": "You generate a single, detailed image prompt from any text input.\nOutput ONLY the final prompt.\nDo NOT explain, do NOT add notes, do NOT include multiple options, do NOT include headings.\nJust one clean prompt.\n\nThe prompt must:\n\nDescribe the visual subject clearly based on the given text.\n\nInclude relevant details: appearance, colors, shapes, materials, environment, mood.\n\nBe realistic and concise, but vivid.\n\nNot add anything unrelated to the user\u2019s text.\n\nYour entire response must be ONLY the prompt. Nothing else."
        },
        "messages": {
          "values": [
            {
              "content": "={{ $('Add the post to the sheet').item.json['LinkedIn_Post '] }}"
            }
          ]
        }
      },
      "credentials": {},
      "typeVersion": 1
    },
    {
      "id": "e9d44b54-274e-4570-92d3-ef60d32f5ac9",
      "name": "Generate image",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -1568,
        -120
      ],
      "parameters": {
        "prompt": "=Generate an image for a linkedin post this is the description:{{ $json.content }} .The images should be realistic for linkedin.",
        "options": {},
        "resource": "image"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "7633d069-1f47-4f0c-8a5d-970758f7e9d9",
      "name": "Create a post",
      "type": "n8n-nodes-base.linkedIn",
      "position": [
        -1344,
        -120
      ],
      "parameters": {
        "authentication": "communityManagement",
        "additionalFields": {},
        "shareMediaCategory": "IMAGE"
      },
      "credentials": {
        "linkedInCommunityManagementOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Filter": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search": {
      "main": [
        [
          {
            "node": "chat input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approved?": {
      "main": [
        [
          {
            "node": "Image Prompt",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Content Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "chat input": {
      "main": [
        [
          {
            "node": "Content Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Image Prompt": {
      "main": [
        [
          {
            "node": "Generate image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approval Email": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate image": {
      "main": [
        [
          {
            "node": "Create a post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Content Generator": {
      "main": [
        [
          {
            "node": "Add the post to the sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ollama Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Content Generator",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets Trigger": {
      "main": [
        [
          {
            "node": "Filter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add the post to the sheet": {
      "main": [
        [
          {
            "node": "Approval Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message a model in Ollama": {
      "ai_tool": [
        [
          {
            "node": "Image Prompt",
            "type": "ai_tool",
            "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 is designed for marketers, content creators, agency owners, and solopreneurs who want to automate LinkedIn content creation using AI. It helps turn Google Sheets entries into complete LinkedIn posts, including text, image prompts, and AI-generated images.

Source: https://n8n.io/workflows/11355/ — 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 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

Who Is This For?

Telegram, Google Sheets Trigger, Lm Chat Mistral Cloud +17
AI & RAG

This workflow automates the process of generating, reviewing, and publishing blog posts across multiple platforms, now enhanced with support for RSS Feeds as a content source. It streamlines the manag

HTTP Request, Html Extract, RSS Feed Read +9
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

Whether you’re a product manager, developer, or simply curious about workflow automation, you’re in the right place. This n8n workflow is designed to help you streamline and automate your social media

Output Parser Structured, OpenAI Chat, LinkedIn +8