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 →
{
"name": "PostStack: AI Image Posts from Trends",
"nodes": [
{
"id": "sticky-overview",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-760,
-400
],
"parameters": {
"color": 1,
"width": 720,
"height": 460,
"content": "## Generate AI image posts from trending topics and publish with PostStack\n\n### How it works\nOn a schedule this workflow picks a trending topic from an RSS feed (Google Trends by default), asks OpenAI to write a caption plus an image prompt in your niche's style (strict JSON), generates the image with OpenAI's image model, and publishes the finished post through [PostStack](https://poststack.techskills.academy) - a self-hosted social media publishing platform - to every channel you list (Instagram, Facebook and more, one API). Fresh, on-trend visual content with zero manual work.\n\n### Setup\n1. In PostStack create an API key (**Settings \u2192 API keys**). API access is a PostStack Pro feature.\n2. Copy the channel ids you want to post to from **Channels**.\n3. Fill in **Configuration (EDIT ME)**: PostStack URL, API key, comma-separated channel ids, topic feed URL and your niche/style brief (drives both the caption and the image).\n4. Connect your OpenAI account on **Write the Concept** and **Generate the Image** (billed by OpenAI - BYOK), and grab a free API key from api.imgbb.com for **Host the Image** (PostStack ingests images from a public URL).\n5. Adjust the **Daily Trigger** schedule and activate.\n\n### Customization tips\nTune `nicheStyle` for a consistent visual identity. Swap the feed for your industry's RSS. Prefer another image model? Replace **Generate the Image** with a Replicate/Flux HTTP call - the only contract is a public image URL handed to **Build Posts per Channel**."
}
},
{
"id": "sticky-topic",
"name": "Section: Pick a Topic",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-760,
340
],
"parameters": {
"color": 7,
"width": 1120,
"height": 220,
"content": "## Pick a Topic\nRuns on a schedule, reads the trends feed and picks the top topic of the day."
}
},
{
"id": "sticky-ai",
"name": "Section: Create with AI",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
440,
340
],
"parameters": {
"color": 7,
"width": 1120,
"height": 220,
"content": "## Create with AI\nOpenAI writes the caption + image prompt, the image model renders the visual, imgbb hosts it."
}
},
{
"id": "sticky-publish",
"name": "Section: Publish via PostStack",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1640,
340
],
"parameters": {
"color": 7,
"width": 1420,
"height": 220,
"content": "## Publish via PostStack\nResolves your channels, creates one post per channel with the generated image and publishes each."
}
},
{
"id": "trigger-1",
"name": "Daily Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-700,
400
],
"parameters": {
"rule": {
"interval": [
{
"field": "days",
"triggerAtHour": 9
}
]
}
},
"notes": "Runs once a day at 09:00. Adjust to your posting rhythm."
},
{
"id": "config-1",
"name": "Configuration (EDIT ME)",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-400,
400
],
"parameters": {
"assignments": {
"assignments": [
{
"id": "a1",
"name": "postStackUrl",
"value": "https://your-poststack.example.com",
"type": "string"
},
{
"id": "a2",
"name": "apiKey",
"value": "rs_live_your-api-key",
"type": "string"
},
{
"id": "a3",
"name": "channelIds",
"value": "channel-uuid-1, channel-uuid-2",
"type": "string"
},
{
"id": "a4",
"name": "topicFeedUrl",
"value": "https://trends.google.com/trending/rss?geo=US",
"type": "string"
},
{
"id": "a5",
"name": "nicheStyle",
"value": "A tech-savvy small business audience. Clean, modern flat illustration style, bold colors, no text in the image.",
"type": "string"
},
{
"id": "a6",
"name": "imgbbApiKey",
"value": "your-free-imgbb-api-key",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {
"stripBinary": false
}
},
"notes": "channelIds: comma-separated ids from PostStack -> Channels. imgbbApiKey: free key from api.imgbb.com - hosts the generated image so PostStack can ingest it (swap for S3/R2 if you prefer)."
},
{
"id": "rss-1",
"name": "Fetch Trending Topics",
"type": "n8n-nodes-base.rssFeedRead",
"typeVersion": 1.1,
"position": [
-100,
400
],
"parameters": {
"url": "={{ $('Configuration (EDIT ME)').item.json.topicFeedUrl }}",
"options": {}
}
},
{
"id": "pick-1",
"name": "Pick a Topic",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
200,
400
],
"parameters": {
"jsCode": "const items = $input.all();\nif (!items.length) throw new Error('The topic feed returned no items');\nconst top = items[0].json;\n\n// Google Trends carries an item picture; other feeds may not - Build Posts falls back to config imageUrl.\nconst picture = top['ht:picture'] || top['media:content'] || null;\n\nreturn [{ json: {\n topic: top.title,\n context: (top.contentSnippet || top.content || '').slice(0, 500),\n link: top.link || '',\n picture,\n} }];"
},
"notes": "Takes the top feed item. Swap for a random pick or a loop over the top N."
},
{
"id": "ai-1",
"name": "Write the Concept",
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 1.8,
"position": [
500,
400
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "gpt-4o-mini"
},
"messages": {
"values": [
{
"role": "system",
"content": "={{ 'You are a social media creative director for: ' + $('Configuration (EDIT ME)').item.json.nicheStyle + ' Reply with STRICT JSON only, no markdown fences: {\"caption\": \"post text, max 400 chars, no hashtags inside\", \"hashtags\": \"#three #relevant #tags\", \"imagePrompt\": \"a detailed prompt for an image generator, matching the style brief\"}' }}"
},
{
"content": "=Create a social media post concept about this trending topic: {{ $json.topic }}\n\nContext: {{ $json.context }}"
}
]
},
"options": {}
},
"notes": "Connect your OpenAI credentials here."
},
{
"id": "parse-1",
"name": "Parse the Concept",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
800,
400
],
"parameters": {
"jsCode": "const out = $input.first().json;\nlet text = (out.message && out.message.content) || out.output || out.text || (out.choices && out.choices[0] && out.choices[0].message.content) || '';\nif (typeof text !== 'string') text = JSON.stringify(text);\n\nlet parsed = {};\ntry {\n const match = text.match(/\\{[\\s\\S]*\\}/);\n parsed = JSON.parse(match ? match[0] : text);\n} catch (e) {\n throw new Error('The model did not return valid JSON: ' + text.slice(0, 200));\n}\nconst caption = String(parsed.caption || '').trim();\nconst imagePrompt = String(parsed.imagePrompt || '').trim();\nif (!caption || !imagePrompt) throw new Error('Missing caption or imagePrompt in the model output');\n\nreturn [{ json: { caption, hashtags: String(parsed.hashtags || '').trim(), imagePrompt } }];"
}
},
{
"id": "img-1",
"name": "Generate the Image",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1100,
400
],
"parameters": {
"method": "POST",
"url": "https://api.openai.com/v1/images/generations",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ model: 'gpt-image-1', prompt: $json.imagePrompt, size: '1024x1024', quality: 'medium' }) }}",
"options": {
"timeout": 180000
}
},
"notes": "Calls the OpenAI Images API directly (gpt-image-1 returns base64). Billed by OpenAI - BYOK."
},
{
"id": "host-1",
"name": "Host the Image",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1400,
400
],
"parameters": {
"method": "POST",
"url": "https://api.imgbb.com/1/upload",
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "key",
"value": "={{ $('Configuration (EDIT ME)').item.json.imgbbApiKey }}"
},
{
"name": "image",
"value": "={{ $json.data[0].b64_json }}"
}
]
},
"options": {
"timeout": 120000
}
},
"notes": "Free image hosting so PostStack has a public URL to ingest. Swap for your own S3/R2 upload if you prefer."
},
{
"id": "channels-1",
"name": "Get Channels",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1700,
400
],
"parameters": {
"method": "GET",
"url": "={{ $('Configuration (EDIT ME)').item.json.postStackUrl }}/api/v1/channels",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $('Configuration (EDIT ME)').item.json.apiKey }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"options": {
"timeout": 30000
}
}
},
{
"id": "plan-1",
"name": "Build Posts per Channel",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2000,
400
],
"parameters": {
"jsCode": "const config = $('Configuration (EDIT ME)').first().json;\nconst draft = $('Parse the Concept').first().json;\nconst image = $('Host the Image').first().json;\nconst channels = $input.first().json.data || [];\n\nconst imageUrl = image.data && image.data.url;\nif (!imageUrl) throw new Error('Image generation returned no URL');\n\nconst baseUrl = String(config.postStackUrl || '').replace(/\\/+$/, '');\nconst wanted = String(config.channelIds || '').split(',').map((s) => s.trim()).filter(Boolean);\nif (!wanted.length) throw new Error('channelIds in Configuration is empty');\n\nconst byId = new Map(channels.map((c) => [c.id, c]));\nreturn wanted.map((id) => {\n const channel = byId.get(id);\n if (!channel) throw new Error(`Channel ${id} not found in PostStack - check channelIds`);\n return { json: {\n channelId: channel.id,\n platform: channel.platform,\n caption: draft.caption,\n hashtags: draft.hashtags,\n imageUrl,\n createUrl: `${baseUrl}/api/v1/posts`,\n } };\n});"
}
},
{
"id": "create-1",
"name": "Create Post in PostStack",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2300,
400
],
"parameters": {
"method": "POST",
"url": "={{ $json.createUrl }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $('Configuration (EDIT ME)').item.json.apiKey }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ platform: $json.platform, description: $json.caption, hashtags: $json.hashtags, mediaUrl: $json.imageUrl }) }}",
"options": {
"timeout": 60000
}
}
},
{
"id": "prep-1",
"name": "Prepare Publish",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2600,
400
],
"parameters": {
"jsCode": "const config = $('Configuration (EDIT ME)').first().json;\nconst baseUrl = String(config.postStackUrl || '').replace(/\\/+$/, '');\n\nreturn $input.all().map((item, i) => {\n const postId = item.json.data && item.json.data.id;\n if (!postId) throw new Error('Post creation did not return an id');\n const plan = $('Build Posts per Channel').all()[i].json;\n return { json: {\n publishUrl: `${baseUrl}/api/v1/posts/${postId}/publish`,\n channelId: plan.channelId,\n channelName: plan.channelName,\n } };\n});"
},
"notes": "Pairs each created post with its target channel. URLs built here - n8n template validation dislikes multi-part expressions in URL fields."
},
{
"id": "publish-1",
"name": "Publish Now",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2900,
400
],
"parameters": {
"method": "POST",
"url": "={{ $json.publishUrl }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $('Configuration (EDIT ME)').item.json.apiKey }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ channelId: $json.channelId, when: 'now' }) }}",
"options": {
"timeout": 60000
}
},
"notes": "Change when to an ISO timestamp to schedule instead of publishing immediately. Pair with the post-alerts workflow to get notified about the outcome."
}
],
"connections": {
"Daily Trigger": {
"main": [
[
{
"node": "Configuration (EDIT ME)",
"type": "main",
"index": 0
}
]
]
},
"Configuration (EDIT ME)": {
"main": [
[
{
"node": "Fetch Trending Topics",
"type": "main",
"index": 0
}
]
]
},
"Fetch Trending Topics": {
"main": [
[
{
"node": "Pick a Topic",
"type": "main",
"index": 0
}
]
]
},
"Pick a Topic": {
"main": [
[
{
"node": "Write the Concept",
"type": "main",
"index": 0
}
]
]
},
"Write the Concept": {
"main": [
[
{
"node": "Parse the Concept",
"type": "main",
"index": 0
}
]
]
},
"Parse the Concept": {
"main": [
[
{
"node": "Generate the Image",
"type": "main",
"index": 0
}
]
]
},
"Generate the Image": {
"main": [
[
{
"node": "Host the Image",
"type": "main",
"index": 0
}
]
]
},
"Get Channels": {
"main": [
[
{
"node": "Build Posts per Channel",
"type": "main",
"index": 0
}
]
]
},
"Build Posts per Channel": {
"main": [
[
{
"node": "Create Post in PostStack",
"type": "main",
"index": 0
}
]
]
},
"Create Post in PostStack": {
"main": [
[
{
"node": "Prepare Publish",
"type": "main",
"index": 0
}
]
]
},
"Prepare Publish": {
"main": [
[
{
"node": "Publish Now",
"type": "main",
"index": 0
}
]
]
},
"Host the Image": {
"main": [
[
{
"node": "Get Channels",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": true
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
PostStack: AI Image Posts from Trends. Uses rssFeedRead, openAi, httpRequest. Scheduled trigger; 17 nodes.
Source: https://github.com/jurczykpawel/poststack/blob/2db876898a7cd87f3816a2fc6611f850d8f5c541/examples/n8n/trends-ai-image-to-social.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Workflow runs at 08:00 (UK), AI stories from TechCrunch, The Verge, and the OpenAI Blog via RSS, AI to select and editorially shape one signal into a post and image prompt, generates and QA-checks a p
News Automation - Main Workflow. Uses rssFeedRead, postgres, openAi, httpRequest. Scheduled trigger; 28 nodes.
RSS Summary. Uses github, discord, openAi, httpRequest. Scheduled trigger; 26 nodes.
Daily Economic News Brief for Israel (Hebrew, RTL, GPT-4o)
This workflow automatically creates a daily market intelligence brief for your stock portfolio. Instead of checking prices, news, and social media separately, it brings everything together into one cl