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 →
{
"id": "zMl1xguA65ATPxMh",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Generate social posts from GitHub pushes to Twitter and LinkedIn",
"tags": [],
"nodes": [
{
"id": "29f468dd-8822-4de7-9bf7-0205e8b51b12",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-640,
-144
],
"parameters": {
"path": "70b80513-b93c-4749-9a06-e78d4f1f2d23",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "a991ef0a-48ef-4ccf-98c6-587edddafa96",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
400,
-80
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "a19b66f5-0927-4c59-8343-f2cea29feb5e",
"name": "Get README",
"type": "n8n-nodes-base.github",
"position": [
64,
-208
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "jorge210488"
},
"filePath": "README.md",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $json.body.repository.name }}"
},
"additionalParameters": {}
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "b3ae8e15-dd37-4207-a78c-4b18b72c9cb8",
"name": "Get CHANGELOG",
"type": "n8n-nodes-base.github",
"position": [
64,
32
],
"parameters": {
"owner": {
"__rl": true,
"mode": "name",
"value": "jorge210488"
},
"filePath": "CHANGELOG.md",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "name",
"value": "={{ $json.body.repository.name }}"
},
"additionalParameters": {}
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "9229a69b-fe35-4a25-a8d8-bc3e1f6d8894",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
-448,
-144
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "fb7ff717-d8a0-44ed-9053-1e88d50f88de",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{\n ($json.body.commits ?? []).some(c =>\n [ ...(c.added ?? []), ...(c.modified ?? []), ...(c.removed ?? []) ]\n .some(f => String(f).toLowerCase().includes('changelog'))\n )\n}}",
"rightValue": "="
}
]
}
},
"typeVersion": 2.2
},
{
"id": "4e9ebbbd-9c37-4dbe-a9b6-dc0f72a85877",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"position": [
-256,
48
],
"parameters": {},
"typeVersion": 1
},
{
"id": "e35435d4-f3f8-44f1-ac78-21efda7076ac",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
560,
-80
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
},
{
"id": "82c95a95-4a00-405d-a826-9c387a3f76be",
"name": "Basic LLM Chain",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
832,
-80
],
"parameters": {
"text": "=README: {{ $json.data[0].data }}\nCHANGELOG: {{ $json.data[1].data }}",
"batching": {},
"messages": {
"messageValues": [
{
"message": "=# Role\n\nYou are a precise content generator for social media.\nYour input will be a JSON object with two string fields:\n\n* `README`: full project README in Markdown.\n* `CHANGELOG`: full changelog in Markdown.\n\n## Your task\n\nProduce **one JSON object** with exactly two keys:\n\n```json\n{\n \"twitter\": \"<tweet in English, <=280 chars including the GitHub link>\",\n \"linkedin\": \"<LinkedIn post in English, long-form>\"\n}\n```\n\nNo extra keys, no surrounding text, no Markdown fences.\n\n## Parsing rules (do not hallucinate)\n\n1. **Repository URL (required in both posts):**\n\n * Extract the **first** URL that matches a GitHub repo pattern from `CHANGELOG` using a regex like:\n `https?://github\\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+`\n * If not found in `CHANGELOG`, search `README` with the same regex.\n * If still not found, **do not fabricate** a URL; instead, use the literal placeholder `https://github.com/REPO_MISSING`. (Keep generating the posts.)\n\n2. **Project name:**\n\n * Prefer the first H1 (`# Title`) in `README`. If not present, infer a concise name from the first heading found. If impossible, use a short neutral name like \u201cThis project\u201d.\n\n3. **First release or update determination:**\n\n * From `CHANGELOG`, detect if this is the **first or only version** (e.g., version numbers like `v1.0.0`, \"Initial release\", or only one version section present).\n * If first/only version \u2192 the post should clearly state it's a **new project/app/development** (e.g., \u201cNew project\u201d, \u201cLaunching a new application\u201d).\n * If not first version \u2192 the post should state it\u2019s an **update or improvement** (e.g., \u201cUpdating the project X\u201d, \u201cImproving the application X\u201d).\n\n4. **Description & features:**\n\n * Derive a clear, one-sentence value proposition from `README`\u2019s Description/Overview.\n * Collect 4\u20137 key capabilities/benefits (bullets for LinkedIn).\n\n5. **Tech stack:**\n\n * From `README`, extract notable technologies (backend, frontend, infra/services).\n * Examples to detect if present: Django, DRF, Celery, Redis, Channels/WebSockets, Next.js, React, TypeScript, Tailwind CSS, Zustand, Framer Motion, Stripe, OpenAI, OAuth/Google, SMTP/Email, Docker, Docker Compose.\n * For **Twitter**, mention only the **3\u20134 most central** technologies.\n * For **LinkedIn**, you may group by Backend / Frontend / Infra.\n\n## LinkedIn post (English, long-form)\n\n* Tone: professional, enthusiastic, concise sentences; **no emojis**.\n* Structure (use short paragraphs and bullet points):\n\n * Hook:\n\n * If first release \u2192 start with \u201cNew project: \u2026\u201d or similar phrasing.\n * If update \u2192 start with \u201cUpdating the project \\[name]: \u2026\u201d or similar phrasing.\n * What it does: 1 short paragraph.\n * Key features/benefits: 4\u20137 bullets.\n * Tech stack (grouped): bullets for Backend, Frontend, Infra/Services.\n * Motivational developer line (one sentence, non-cheesy).\n * Call to action with the **repo link** (exactly once).\n* Hashtags: **8\u201312** relevant tags, all lowercase, no spaces (use hyphens only if part of the tech brand). Prioritize technologies and domain, e.g.:\n `#python #django #nextjs #typescript #tailwindcss #stripe #openai #celery #redis #websockets #ai #saas #startups #webdevelopment`\n (Pick only those truly present; don\u2019t invent.)\n\n## Twitter post (English, max 280 chars including link)\n\n* Format:\n\n * If first release \u2192 short sentence like \u201cNew project: \u2026\u201d or similar.\n * If update \u2192 short sentence like \u201cUpdating project \\[name]: \u2026\u201d or similar.\n* Describe what the app does + **repo link** (once).\n* Mention **3\u20134** main technologies inline (e.g., Django, Next.js, Stripe, OpenAI).\n* **2\u20133 hashtags max**, chosen from the most relevant.\n* **No emojis.**\n* Length enforcement: if >280 chars, iteratively shorten by (in order): remove adjectives, compress wording, then drop one hashtag at a time until \u2264280. Never drop the repo link.\n\n## Output formatting\n\n* Return a **single JSON object** with keys `\"twitter\"` and `\"linkedin\"`.\n* Escape quotes properly.\n* Do **not** add explanations, markdown, or extra whitespace before/after the JSON.\n* Do not include any links other than the repo link.\n\n## Safety & accuracy\n\n* Never fabricate tech or features not present in the inputs.\n* If some sections are missing in inputs, omit that detail gracefully.\n* Keep everything in **English** except hashtags which are typically lowercase English tokens.\n* Do not output dates/releases unless explicitly present.\n* Maintain a positive, credible tone.\n"
}
]
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.7
},
{
"id": "927afa09-65bf-4c62-955c-42a3bb098f28",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
832,
80
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "43f2f142-8319-445f-80a9-61c56ae6ddad",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
1008,
80
],
"parameters": {
"jsonSchemaExample": "{\n \"twitter\": \"tweet in English, <=280 chars including the GitHub link\",\n \"linkedin\": \"LinkedIn post in English, long-form\"\n}"
},
"typeVersion": 1.2
},
{
"id": "c2454817-d07d-45e0-8c5f-03a68c619eb8",
"name": "Post tweet",
"type": "n8n-nodes-base.twitter",
"position": [
1248,
48
],
"parameters": {
"text": "={{ $json.output.twitter }}",
"additionalFields": {}
},
"credentials": {
"twitterOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "944f1559-f79e-4034-b70f-0246c8b40a5f",
"name": "LinkedIn",
"type": "n8n-nodes-base.linkedIn",
"position": [
1248,
-160
],
"parameters": {
"text": "={{ $json.output.linkedin }}",
"person": "nUdV-_cHkk",
"additionalFields": {}
},
"credentials": {
"linkedInOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "d64cf42e-40b0-4db8-9748-774751840a14",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-720,
-720
],
"parameters": {
"width": 660,
"height": 900,
"content": "# GitHub Push \u2192 README & CHANGELOG check\n\n## 1) Webhook (receive push from GitHub)\n* **Node:** Webhook (POST)\n* **GitHub setup:** Repository \u2192 *Settings \u2192 Webhooks \u2192 Add webhook*\n * **Payload URL:** `https://<your-n8n-domain>/webhook/github/push`\n * **Content type:** `application/json`\n * **Event:** *Just the push event*\n * **Secret:** optional\n * **Branches:** choose specific branches or send all\n## 2) IF (filter)\n* **Node:** IF\n* **Checks:** push contains `README` and `CHANGELOG` in *added* or *modified* files\n* **True:** continue\n* **False:** do nothing (stop)\n"
},
"typeVersion": 1
},
{
"id": "b08176e7-283f-4ed3-8c54-c90daeb05d59",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
48,
-720
],
"parameters": {
"color": 4,
"width": 640,
"height": 920,
"content": "# GitHub \u2192 Extract \u2192 Merge/Aggregate\n\n## 1) GitHub (Get Repository File)\n* **Credentials:** GitHub OAuth2 or PAT with read access.\n* **Owner/Repo/Branch:** from webhook payload (`body.repository`, branch from `body.ref`).\n* **File Path:** `README.md` and `CHANGELOG.md` (use two GitHub nodes).\n* **Output:** binary files.\n## 2) Extract from File (text)\n* **Operation:** `text`.\n* **Input:** binary from each GitHub node.\n* **Output:** plain text for README and CHANGELOG.\n## 3) Merge & Aggregate\n* **Merge:** *Wait for Both* (combine README + CHANGELOG into one item).\n* **Aggregate:** `aggregateAllItemData` (single item with `documents` list holding both contents).\n"
},
"typeVersion": 1
},
{
"id": "90e42f76-4ca3-4e6d-902c-2b4f1e29c0a4",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
752,
-720
],
"parameters": {
"width": 720,
"height": 920,
"content": "# LLM \u2192 Post to Twitter & LinkedIn\n\n## 1) LLM (OpenAI \u2192 JSON)\n* Nodes: OpenAI Chat Model \u2192 Basic LLM Chain (+ Structured Output Parser)\n* Input: aggregated README + CHANGELOG\n* Output: JSON with `\"twitter\"` and `\"linkedin\"`\n## 2) Publish\n* **Twitter node**\n * Auth: credentials\n* **LinkedIn node (Person)**\n * Auth: credentials\n * Required scope: `w_member_social`, `openid`\n"
},
"typeVersion": 1
},
{
"id": "3f31d48b-c0cc-4140-9bd8-02f4267f7f65",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1520,
-720
],
"parameters": {
"color": 5,
"width": 780,
"height": 900,
"content": "# Generate social posts from GitHub pushes to Twitter and LinkedIn\nOn each GitHub *push*, this workflow checks if the commit set includes **README.md** and **CHANGELOG.md**, fetches both files, lets an **LLM** generate a Twitter and LinkedIn post, then publishes to **Twitter** and **LinkedIn (Person)**.\n## Apps & Nodes\n* **Trigger:** Webhook\n* **Logic:** IF, Merge, Aggregate\n* **GitHub:** Get Repository File (\u00d72)\n* **Files:** Extract from File (text) (\u00d72)\n* **AI:** OpenAI Chat Model \u2192 LLM Chain (+ Structured Output Parser)\n* **Publish:** Twitter, LinkedIn (Person)\n## Prerequisites\n* **GitHub:** OAuth2 or PAT with repo read.\n* **OpenAI:** API key.\n* **Twitter:** OAuth2 app with *Read and Write*; scopes `tweet.read tweet.write users.read offline.access`.\n* **LinkedIn (Person):** OAuth2 credentials; **required scope:** `w_member_social`, `openid`.\n## Setup\n1. **GitHub Webhook:** Repo \u2192 *Settings \u2192 Webhooks*\n\n * Payload URL: `https://<your-n8n-domain>/webhook/github/push`\n * Content type: `application/json` \u2022 Event: *Push* \u2022 Secret (optional) \u2022 Branches as needed.\n2. **Credentials:** Connect GitHub, OpenAI, Twitter, and LinkedIn (Person).\n## How it Works\n1. **Webhook** receives GitHub push payload.\n2. **IF** checks that `README` and `CHANGELOG` appear in *added/modified*.\n3. **GitHub (Get Repository File)** pulls `README.md` and `CHANGELOG.md`.\n4. **Extract from File (text)** converts both binaries to text.\n5. **Merge & Aggregate** combines into one item with both contents.\n6. **LLM (OpenAI + Parser)** returns a JSON with `twitter` and `linkedin`.\n7. **Twitter** posts the tweet.\n8. **LinkedIn (Person)** posts the LinkedIn text.\n"
},
"typeVersion": 1
},
{
"id": "95134ca6-76d8-49a2-ae2c-6f1d7121c9a6",
"name": "Extract from File README",
"type": "n8n-nodes-base.extractFromFile",
"position": [
224,
-208
],
"parameters": {
"options": {},
"operation": "text"
},
"typeVersion": 1
},
{
"id": "a61fd00a-34f2-4e8b-bdf5-7e9c686f58ed",
"name": "Extract from File CHANGELOG",
"type": "n8n-nodes-base.extractFromFile",
"position": [
224,
32
],
"parameters": {
"options": {},
"operation": "text"
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "78f3c247-4dc6-474b-8a51-3a55a072dd92",
"connections": {
"If": {
"main": [
[
{
"node": "Get CHANGELOG",
"type": "main",
"index": 0
},
{
"node": "Get README",
"type": "main",
"index": 0
}
],
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Basic LLM Chain",
"type": "main",
"index": 0
}
]
]
},
"Get README": {
"main": [
[
{
"node": "Extract from File README",
"type": "main",
"index": 0
}
]
]
},
"Get CHANGELOG": {
"main": [
[
{
"node": "Extract from File CHANGELOG",
"type": "main",
"index": 0
}
]
]
},
"Basic LLM Chain": {
"main": [
[
{
"node": "Post tweet",
"type": "main",
"index": 0
},
{
"node": "LinkedIn",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Basic LLM Chain",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Extract from File README": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "Basic LLM Chain",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Extract from File CHANGELOG": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
}
}
}
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.
githubApilinkedInOAuth2ApiopenAiApitwitterOAuth2Api
About this workflow
Generate social posts from GitHub pushes to Twitter and LinkedIn. Uses github, noOp, chainLlm, lmChatOpenAi. Webhook trigger; 18 nodes.
Source: https://github.com/ScraperNode/awesome-n8n-templates/blob/main/templates/ai-and-llm/7562-auto-generate-social-posts-from-github-readmechangelog-updates-with-gpt-4o-and-o/workflow.json — original creator credit. Request a take-down →