AutomationFlowsSocial Media › Create Instagram Carousels and Tiktok Slideshows with Openai and Postfast

Create Instagram Carousels and Tiktok Slideshows with Openai and Postfast

ByPetar Georgiev @peturgeorgievv on n8n.io

This workflow runs on a Mon/Wed/Fri schedule to generate an Instagram carousel or TikTok slideshow using OpenAI (GPT-4o-mini and gpt-image-1), sends the slides to Telegram for approval, and only then uploads the images and schedules the post via PostFast. Runs every Monday,…

Cron / scheduled trigger★★★★☆ complexityAI-powered23 nodesN8N Nodes PostfastOpenAITelegramHTTP Request
Social Media Trigger: Cron / scheduled Nodes: 23 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the HTTP Request → OpenAI 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": "Generate Instagram carousels and TikTok slideshows with GPT-Image-1 and PostFast after Telegram approval",
  "nodes": [
    {
      "id": "t4-sticky-main",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        16
      ],
      "parameters": {
        "width": 544,
        "height": 1104,
        "content": "## Generate Instagram carousels and TikTok slideshows with GPT-Image-1 and PostFast after Telegram approval\n\n### How it works\n\nRuns Monday, Wednesday and Friday mornings. OpenAI writes a carousel (caption plus slide headlines and text), gpt-image-1 renders every slide as a portrait image, and the finished slides land in your Telegram with Approve and Decline buttons. Nothing is posted until you approve. On approval each selected account gets its own uploaded copy of every slide and one scheduled post via [PostFast](https://postfa.st): a real carousel on Instagram, a photo slideshow on TikTok.\n\n### Setup steps\n\n- Install the verified PostFast community node (n8n Cloud: enable Verified Community Nodes in the Admin Panel; self-hosted: install n8n-nodes-postfast).\n- Connect your [PostFast](https://postfa.st) credentials (API key from Settings > API) on the three PostFast nodes.\n- Connect your OpenAI credentials on both OpenAI nodes.\n- Create a Telegram bot with @BotFather, paste its token into a Telegram credential on both Telegram nodes, then open a chat with your bot and press Start (bots cannot message you first).\n- Get your chat ID: send any message in Telegram, forward it to @myidbot (IDBot), and it replies with your numeric ID. Put that number in the configuration node. Alternative: message your bot, then open api.telegram.org/botYOUR_TOKEN/getUpdates and read chat.id.\n- Fill in the configuration node: brand, voice, topics, slide count, image style, platforms.\n- Run once manually to test safely: nothing is ever posted until you tap Approve in Telegram, so tapping Decline is a free end-to-end test. When you do approve, check the scheduled posts in your PostFast dashboard before activating.\n\n### Customization\n\nChange the slide count (2 to 8), image style, topics or posting days. In the PostFast scheduler node's Controls you can enable TikTok auto music and the AI-generated content label. Prefer reviewing in a dashboard instead of Telegram? Set approvalStatus to PENDING_APPROVAL and approve posts inside PostFast."
      },
      "typeVersion": 1
    },
    {
      "id": "t4-sticky-1",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 400,
        "content": "## 1. Start and configure\n\nTriggers three mornings a week. The configuration node is the only node you have to edit: brand, voice, topics, slide count, image style, platform filter and your Telegram chat ID all live here."
      },
      "typeVersion": 1
    },
    {
      "id": "t4-sticky-2",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 400,
        "content": "## 2. Accounts and carousel writing\n\nFetches your connected PostFast accounts and keeps the platforms you chose. OpenAI then writes the caption and every slide as strict JSON. Want a different tone or language? Edit the prompt in the OpenAI node."
      },
      "typeVersion": 1
    },
    {
      "id": "t4-sticky-3",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1968,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 400,
        "content": "## 3. Render the slides\n\nEach slide becomes a 1024x1536 portrait gpt-image-1 image with the headline rendered on it, then every slide is sent to your Telegram as a preview. The look and feel comes from imageStyle in the configuration."
      },
      "typeVersion": 1
    },
    {
      "id": "t4-sticky-4",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2832,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 544,
        "content": "## 4. Approve before it posts\n\nOne Telegram message with Approve and Decline buttons, sent by your own bot. The workflow stays paused until you tap. Decline discards the run: nothing is uploaded, nothing is scheduled."
      },
      "typeVersion": 1
    },
    {
      "id": "t4-sticky-5",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3456,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 1392,
        "height": 400,
        "content": "## 5. Upload and schedule per account\n\nOn approval every account gets its own upload URLs and its own copy of each slide, then one scheduled post per account with slides in order. Instagram publishes a carousel, TikTok a photo slideshow."
      },
      "typeVersion": 1
    },
    {
      "id": "t4-trigger",
      "name": "Every Mon Wed Fri at 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        656,
        240
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1,
                3,
                5
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "t4-config",
      "name": "Configure carousel brand and topics",
      "type": "n8n-nodes-base.set",
      "position": [
        864,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c1",
              "name": "brandName",
              "type": "string",
              "value": "YOUR BRAND NAME"
            },
            {
              "id": "c2",
              "name": "brandVoice",
              "type": "string",
              "value": "friendly, practical, no hype"
            },
            {
              "id": "c3",
              "name": "topics",
              "type": "string",
              "value": "topic one, topic two, topic three"
            },
            {
              "id": "c4",
              "name": "slideCount",
              "type": "number",
              "value": 5
            },
            {
              "id": "c5",
              "name": "imageStyle",
              "type": "string",
              "value": "clean flat vector illustration, soft pastel palette, bold dark text on light background"
            },
            {
              "id": "c6",
              "name": "platformFilter",
              "type": "string",
              "value": "INSTAGRAM, TIKTOK"
            },
            {
              "id": "c7",
              "name": "telegramChatId",
              "type": "string",
              "value": "YOUR TELEGRAM CHAT ID"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "t4-accounts",
      "name": "Get connected accounts (PostFast)",
      "type": "n8n-nodes-postfast.postFast",
      "position": [
        1168,
        240
      ],
      "parameters": {
        "resource": "socialAccount",
        "operation": "getAll"
      },
      "typeVersion": 1
    },
    {
      "id": "t4-select",
      "name": "Select target accounts",
      "type": "n8n-nodes-base.code",
      "position": [
        1392,
        240
      ],
      "parameters": {
        "jsCode": "// Keep only connected accounts on the platforms chosen in the configuration node.\nconst cfg = $('Configure carousel brand and topics').first().json;\nconst wanted = String(cfg.platformFilter || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\nconst accounts = $input.all().map(i => i.json);\n\nconst selected = accounts.filter(a => {\n  const status = String(a.connectionStatus || 'CONNECTED').toUpperCase();\n  const platform = String(a.platform || '').toUpperCase();\n  return status === 'CONNECTED' && (wanted.length === 0 || wanted.includes(platform));\n});\n\nif (selected.length === 0) {\n  throw new Error('No connected accounts matched the platform filter. Check the configuration node and your PostFast connections.');\n}\n\nreturn [{ json: {\n  accountIds: selected.map(a => a.id ?? a.socialMediaId)\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "t4-write",
      "name": "Write the carousel post",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1616,
        240
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You are a social media designer and copywriter for {{ $('Configure carousel brand and topics').first().json.brandName }}. Voice: {{ $('Configure carousel brand and topics').first().json.brandVoice }}.\n\nCreate ONE carousel post about one of these topics: {{ $('Configure carousel brand and topics').first().json.topics }}.\n\nRules: exactly {{ $('Configure carousel brand and topics').first().json.slideCount }} slides. Slide 1 heading must be a scroll-stopping hook. The last slide must be a clear call to action. Slides must flow as one story.\n\nReturn ONLY valid JSON in this exact shape: {\"caption\": \"engaging 60-150 word caption with 2-3 hashtags\", \"slides\": [{\"heading\": \"3-6 word slide headline\", \"body\": \"one short supporting sentence, max 15 words\", \"visual\": \"short scene or background description for the image\"}]}"
            }
          ]
        },
        "jsonOutput": true
      },
      "typeVersion": 1.8
    },
    {
      "id": "t4-prep",
      "name": "Prepare slide prompts",
      "type": "n8n-nodes-base.code",
      "position": [
        2032,
        240
      ],
      "parameters": {
        "jsCode": "// One item per slide, each with a composed image prompt.\nconst cfg = $('Configure carousel brand and topics').first().json;\nconst raw = $input.first().json;\nlet parsed = raw.message?.content ?? raw.output ?? raw;\nif (typeof parsed === 'string') parsed = JSON.parse(parsed);\nconst slides = parsed.slides || [];\nif (slides.length === 0) throw new Error('OpenAI returned no slides. Check the prompt output.');\n\nconst n = Math.min(slides.length, Number(cfg.slideCount) || 5);\nif (n < 2 || n > 8) throw new Error('slideCount must be between 2 and 8 (the upload endpoint issues at most 8 URLs per request).');\n\nreturn slides.slice(0, n).map((s, i) => ({ json: {\n  slideIndex: i,\n  imagePrompt: `${cfg.imageStyle}. Carousel slide ${i + 1} of ${n}, portrait format. Render this text large, centered and highly readable on the image. Headline: \"${s.heading}\". Supporting line: \"${s.body}\". Scene: ${s.visual}. No watermarks, no logos.`\n} }));"
      },
      "typeVersion": 2
    },
    {
      "id": "t4-image",
      "name": "Generate slide images",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        2256,
        240
      ],
      "parameters": {
        "prompt": "={{ $json.imagePrompt }}",
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-image-1",
          "cachedResultName": "gpt-image-1"
        },
        "options": {
          "size": "1024x1536"
        },
        "resource": "image"
      },
      "typeVersion": 2.3
    },
    {
      "id": "t4-preview",
      "name": "Preview slides in Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2576,
        240
      ],
      "parameters": {
        "chatId": "={{ $('Configure carousel brand and topics').first().json.telegramChatId }}",
        "operation": "sendPhoto",
        "binaryData": true,
        "additionalFields": {},
        "binaryPropertyName": "data"
      },
      "typeVersion": 1.2
    },
    {
      "id": "t4-approve",
      "name": "Ask for approval on Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2896,
        240
      ],
      "parameters": {
        "chatId": "={{ $('Configure carousel brand and topics').first().json.telegramChatId }}",
        "message": "=New carousel ready for {{ $('Select target accounts').first().json.accountIds.length }} account(s). The slides are above.\n\nCaption:\n{{ $('Write the carousel post').first().json.message.content.caption }}\n\nApprove to upload and schedule, or decline to discard this run.",
        "options": {},
        "operation": "sendAndWait",
        "approvalOptions": {
          "values": {
            "approvalType": "double"
          }
        }
      },
      "executeOnce": true,
      "typeVersion": 1.2
    },
    {
      "id": "t4-if",
      "name": "Carousel approved?",
      "type": "n8n-nodes-base.if",
      "position": [
        3120,
        240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a1",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "t4-declined",
      "name": "Declined, nothing posted",
      "type": "n8n-nodes-base.noOp",
      "position": [
        3264,
        400
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "t4-fanout",
      "name": "Fan out per account",
      "type": "n8n-nodes-base.code",
      "position": [
        3520,
        240
      ],
      "parameters": {
        "jsCode": "// One item per selected account.\nconst accountIds = $('Select target accounts').first().json.accountIds;\nreturn accountIds.map(id => ({ json: { accountId: id } }));"
      },
      "typeVersion": 2
    },
    {
      "id": "t4-uploadurl",
      "name": "Get upload URLs (PostFast)",
      "type": "n8n-nodes-postfast.postFast",
      "position": [
        3744,
        240
      ],
      "parameters": {
        "count": "={{ $('Prepare slide prompts').all().length }}",
        "resource": "file",
        "contentType": "image/png"
      },
      "typeVersion": 1
    },
    {
      "id": "t4-pair",
      "name": "Pair slides to upload URLs",
      "type": "n8n-nodes-base.code",
      "position": [
        3968,
        240
      ],
      "parameters": {
        "jsCode": "// Pair every slide image with one upload URL per account and attach the binary.\nconst accounts = $('Fan out per account').all().map(i => i.json.accountId);\nconst slides = $('Generate slide images').all();\n\nconst flat = [];\nfor (const it of $('Get upload URLs (PostFast)').all()) {\n  const j = it.json;\n  if (Array.isArray(j)) flat.push(...j);\n  else if (Array.isArray(j.data)) flat.push(...j.data);\n  else if (Array.isArray(j.urls)) flat.push(...j.urls);\n  else flat.push(j);\n}\n\nconst need = accounts.length * slides.length;\nif (flat.length < need) {\n  throw new Error(`Got ${flat.length} upload URLs but need ${need} (${accounts.length} accounts x ${slides.length} slides). Check the Get upload URLs node.`);\n}\n\nconst out = [];\naccounts.forEach((accountId, ai) => {\n  slides.forEach((s, si) => {\n    const u = flat[ai * slides.length + si];\n    out.push({ json: { accountId, slideIndex: si, signedUrl: u.signedUrl, key: u.key }, binary: s.binary });\n  });\n});\nreturn out;"
      },
      "typeVersion": 2
    },
    {
      "id": "t4-put",
      "name": "Upload slides to storage",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4192,
        240
      ],
      "parameters": {
        "url": "={{ $json.signedUrl }}",
        "method": "PUT",
        "options": {},
        "sendBody": true,
        "contentType": "binaryData",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "image/png"
            }
          ]
        },
        "inputDataFieldName": "data"
      },
      "typeVersion": 4.2
    },
    {
      "id": "t4-build",
      "name": "Build carousel posts",
      "type": "n8n-nodes-base.code",
      "position": [
        4416,
        240
      ],
      "parameters": {
        "jsCode": "// One scheduled carousel per account, slides in order via sortOrder.\nconst caption = $('Write the carousel post').first().json.message.content.caption;\nconst items = $('Pair slides to upload URLs').all().map(i => i.json);\nconst when = new Date(Date.now() + 60 * 60 * 1000).toISOString();\n\nconst byAccount = {};\nfor (const it of items) {\n  (byAccount[it.accountId] ??= []).push(it);\n}\n\nreturn Object.entries(byAccount).map(([accountId, slides]) => ({ json: {\n  socialMediaId: accountId,\n  content: caption,\n  scheduledAt: when,\n  mediaItemsJson: JSON.stringify(\n    slides\n      .sort((a, b) => a.slideIndex - b.slideIndex)\n      .map(s => ({ key: s.key, type: 'IMAGE', sortOrder: s.slideIndex }))\n  )\n} }));"
      },
      "typeVersion": 2
    },
    {
      "id": "t4-create",
      "name": "Schedule carousels via PostFast",
      "type": "n8n-nodes-postfast.postFast",
      "position": [
        4640,
        240
      ],
      "parameters": {
        "posts": {
          "post": [
            {
              "content": "={{ $json.content }}",
              "scheduledAt": "={{ $json.scheduledAt }}",
              "socialMediaId": "={{ $json.socialMediaId }}",
              "mediaInputMode": "json",
              "mediaItemsJson": "={{ $json.mediaItemsJson }}"
            }
          ]
        },
        "status": "SCHEDULED",
        "controls": {},
        "resource": "socialPost",
        "operation": "create",
        "approvalStatus": "APPROVED"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Carousel approved?": {
      "main": [
        [
          {
            "node": "Fan out per account",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Declined, nothing posted",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fan out per account": {
      "main": [
        [
          {
            "node": "Get upload URLs (PostFast)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build carousel posts": {
      "main": [
        [
          {
            "node": "Schedule carousels via PostFast",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate slide images": {
      "main": [
        [
          {
            "node": "Preview slides in Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare slide prompts": {
      "main": [
        [
          {
            "node": "Generate slide images",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select target accounts": {
      "main": [
        [
          {
            "node": "Write the carousel post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write the carousel post": {
      "main": [
        [
          {
            "node": "Prepare slide prompts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every Mon Wed Fri at 8am": {
      "main": [
        [
          {
            "node": "Configure carousel brand and topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload slides to storage": {
      "main": [
        [
          {
            "node": "Build carousel posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get upload URLs (PostFast)": {
      "main": [
        [
          {
            "node": "Pair slides to upload URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pair slides to upload URLs": {
      "main": [
        [
          {
            "node": "Upload slides to storage",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preview slides in Telegram": {
      "main": [
        [
          {
            "node": "Ask for approval on Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ask for approval on Telegram": {
      "main": [
        [
          {
            "node": "Carousel approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get connected accounts (PostFast)": {
      "main": [
        [
          {
            "node": "Select target accounts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure carousel brand and topics": {
      "main": [
        [
          {
            "node": "Get connected accounts (PostFast)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow runs on a Mon/Wed/Fri schedule to generate an Instagram carousel or TikTok slideshow using OpenAI (GPT-4o-mini and gpt-image-1), sends the slides to Telegram for approval, and only then uploads the images and schedules the post via PostFast. Runs every Monday,…

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

More Social Media workflows → · Browse all categories →

Related workflows

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

Social Media

Generate Instagram Content from Top Trends with AI Image Generation. Uses telegram, splitInBatches, scheduleTrigger, stickyNote. Scheduled trigger; 44 nodes.

Telegram, HTTP Request, Postgres +2
Social Media

Generate Instagram Content from Top Trends with AI Image Generation. Uses telegram, splitInBatches, scheduleTrigger, stickyNote. Scheduled trigger; 44 nodes.

Telegram, HTTP Request, Postgres +2
Social Media

This automated workflow discovers trending Instagram posts and creates similar AI-generated content. Here's the high-level process: Scrapes trending posts from specific hashtags Analyzes visual elemen

Telegram, HTTP Request, Postgres +2
Social Media

28-generate-instagram-content-from-top-trends-with-ai-image-generation. Uses telegram, httpRequest, postgres, openAi. Scheduled trigger; 44 nodes.

Telegram, HTTP Request, Postgres +2
Social Media

Generate Instagram Content from Top Trends with AI Image Generation. Uses telegram, httpRequest, postgres, openAi. Scheduled trigger; 44 nodes.

Telegram, HTTP Request, Postgres +2