{
  "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
          }
        ]
      ]
    }
  }
}