AutomationFlowsAI & RAG › Repurpose Blog & Youtube Content to Social Media with Gpt-5.1 and Google Docs

Repurpose Blog & Youtube Content to Social Media with Gpt-5.1 and Google Docs

ByAK Pasnoor @ak-pasnoor on n8n.io

Transform blog posts, YouTube videos, or any text into LinkedIn posts, Twitter threads, email newsletters, and more with GPT-5.1 Content creators who want to maximize reach from every piece of content Marketing teams repurposing long-form content for social media Founders &…

Event trigger★★★★☆ complexityAI-powered21 nodesForm TriggerHTTP RequestOpenAIGoogle Docs
AI & RAG Trigger: Event Nodes: 21 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Form Trigger → Google Docs 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": "5b076752-58e8-4bc4-b503-c0755685c2c1",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1552,
        -32
      ],
      "parameters": {
        "color": 5,
        "width": 340,
        "height": 1028,
        "content": "## \ud83c\udfa8 AI Content Repurposing Engine\n\nTransform any content into multiple formats for social media, email, and more.\n\n---\n\n### What It Does\n1. **Input:** Blog URL, YouTube URL, or raw text\n2. **Extract:** Scrapes article or fetches video transcript\n3. **Generate:** AI creates 7 content variations\n4. **Output:** Formatted Google Doc with everything\n\n---\n\n### Content Generated\n- 3x LinkedIn posts (hook + body + CTA)\n- Twitter/X thread (7 tweets)\n- Email newsletter (3 subject lines + body)\n- 5 Key takeaways\n- 5 Quote cards for graphics\n- 60-second video script\n\n---\n\n### Required Credentials\n- **OpenAI** - GPT-4o for content generation\n- **Google Docs** - Output destination\n\n### Optional Credentials\n- **YouTube API** - Better video metadata\n\n---\n\n### Setup\n1. Add OpenAI credential\n2. Add Google Docs credential\n3. (Optional) Add YouTube API key\n4. Activate workflow\n5. Fill out form & get your content!\n\n---\n\n### Tips\n- Longer content = better output\n- Blog posts typically work best\n- YouTube videos need captions enabled\n- Add target audience for better results\n\n---\n\n**Created by [Agentical AI](https://agenticalai.com)**\n\n\ud83d\udca1 Need custom automation? Visit us!"
      },
      "typeVersion": 1
    },
    {
      "id": "38d24169-79d5-4270-9878-c30a7be937d5",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1168,
        16
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 248,
        "content": "### 1\ufe0f\u20e3 Content Input\n\nForm accepts 3 input types:\n\n**Blog URL** \u2192 Fetched & scraped\n**YouTube URL** \u2192 Transcript extracted\n**Raw Text** \u2192 Direct input\n\nOptional fields:\n- Target audience\n- Custom instructions"
      },
      "typeVersion": 1
    },
    {
      "id": "195feb22-33c6-4091-b99c-bac66790f1a6",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        -48
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 220,
        "content": "### 2\ufe0f\u20e3 Content Extraction\n\n**Blog Path:**\nFetch HTML \u2192 Extract article content \u2192 Clean text\n\n**YouTube Path:**\nExtract video ID \u2192 Get metadata via API \u2192 Fetch transcript \u2192 Combine\n\n**Raw Text:**\nPass through with metadata\n\nAll paths merge into unified format for AI."
      },
      "typeVersion": 1
    },
    {
      "id": "4b56b137-4620-423a-8be2-dc097d148398",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        -16
      ],
      "parameters": {
        "color": 4,
        "width": 300,
        "height": 260,
        "content": "### 3\ufe0f\u20e3 AI Content Generation\n\nGPT analyzes content and generates:\n\n\u2705 **LinkedIn** - 3 variations with hooks\n\u2705 **Twitter** - 7-tweet thread\n\u2705 **Email** - Subject lines + newsletter\n\u2705 **Takeaways** - 5 key points\n\u2705 **Quotes** - 5 shareable snippets\n\u2705 **Video Script** - 60-sec format\n\nAI adapts tone to match source content."
      },
      "typeVersion": 1
    },
    {
      "id": "54c17b02-444c-4525-b3bd-52d19d74e02d",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        608,
        16
      ],
      "parameters": {
        "color": 6,
        "width": 280,
        "height": 228,
        "content": "### 4\ufe0f\u20e3 Google Docs Output\n\nCreates formatted document:\n- Source info & summary\n- All generated content\n- Organized by platform\n- Ready to copy/paste\n\nNew doc available in drive."
      },
      "typeVersion": 1
    },
    {
      "id": "021ef226-80f6-4c55-873e-ab59ddbd97b7",
      "name": "Content Input Form",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -1120,
        304
      ],
      "parameters": {
        "path": "content-repurpose",
        "options": {},
        "formTitle": "\ud83c\udfa8 AI Content Repurposing Engine",
        "formFields": {
          "values": [
            {
              "fieldType": "dropdown",
              "fieldLabel": "Content Type",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Blog Post URL"
                  },
                  {
                    "option": "YouTube Video URL"
                  },
                  {
                    "option": "Raw Text / Paste Content"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldType": "textarea",
              "fieldLabel": "Content Input",
              "placeholder": "Paste your URL or content here...\n\nExamples:\n\u2022 https://yourblog.com/article-title\n\u2022 https://youtube.com/watch?v=VIDEO_ID\n\u2022 Or paste your text directly",
              "requiredField": true
            },
            {
              "fieldLabel": "Target Audience (Optional)",
              "placeholder": "e.g., SaaS founders, marketing managers, developers..."
            },
            {
              "fieldType": "textarea",
              "fieldLabel": "Additional Instructions (Optional)",
              "placeholder": "Any specific requirements?\n\ne.g., Focus on the ROI angle, keep it casual, emphasize the technical details..."
            }
          ]
        },
        "formDescription": "Transform your blog posts, YouTube videos, or any text into LinkedIn posts, Twitter threads, email newsletters, and more!\n\nPowered by GPT-4o \u2022 Output saved to Google Docs"
      },
      "typeVersion": 2.1
    },
    {
      "id": "bcb415c7-8f8f-42dc-9b89-09a27c4fab44",
      "name": "Is Blog URL?",
      "type": "n8n-nodes-base.if",
      "position": [
        -880,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "blog-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json['Content Type'] }}",
              "rightValue": "Blog Post URL"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "8eec62ef-8fd0-422b-873a-694758599bd2",
      "name": "Is YouTube URL?",
      "type": "n8n-nodes-base.if",
      "position": [
        -880,
        544
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "youtube-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json['Content Type'] }}",
              "rightValue": "YouTube Video URL"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "035512ee-bffa-489b-b9ff-11349324cef6",
      "name": "Fetch Blog Page",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -496,
        224
      ],
      "parameters": {
        "url": "={{ $('Content Input Form').item.json['Content Input'].trim() }}",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "390eba10-9978-4983-8d06-1fdce3056bfd",
      "name": "Extract Blog Content",
      "type": "n8n-nodes-base.code",
      "position": [
        -240,
        224
      ],
      "parameters": {
        "jsCode": "// Extract main content from HTML\nconst input = $('Content Input Form').first().json;\nconst response = $input.first().json;\nconst html = response.data || response.body || (typeof response === 'string' ? response : '');\n\nlet title = '';\nlet content = '';\nlet description = '';\nlet author = '';\n\ntry {\n  // Extract title\n  const titleMatch = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n  const ogTitleMatch = html.match(/<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']+)[\"']/i);\n  title = ogTitleMatch ? ogTitleMatch[1].trim() : (titleMatch ? titleMatch[1].trim() : '');\n  \n  // Extract meta description\n  const descMatch = html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n                    html.match(/<meta[^>]*property=[\"']og:description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n                    html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*name=[\"']description[\"']/i);\n  description = descMatch ? descMatch[1].trim() : '';\n  \n  // Extract author\n  const authorMatch = html.match(/<meta[^>]*name=[\"']author[\"'][^>]*content=[\"']([^\"']+)[\"']/i);\n  author = authorMatch ? authorMatch[1].trim() : '';\n  \n  // Remove non-content elements\n  let cleanHtml = html\n    .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n    .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '')\n    .replace(/<nav[^>]*>[\\s\\S]*?<\\/nav>/gi, '')\n    .replace(/<header[^>]*>[\\s\\S]*?<\\/header>/gi, '')\n    .replace(/<footer[^>]*>[\\s\\S]*?<\\/footer>/gi, '')\n    .replace(/<aside[^>]*>[\\s\\S]*?<\\/aside>/gi, '')\n    .replace(/<noscript[^>]*>[\\s\\S]*?<\\/noscript>/gi, '')\n    .replace(/<!--[\\s\\S]*?-->/gi, '');\n  \n  // Try to find article content (priority order)\n  const articleMatch = cleanHtml.match(/<article[^>]*>([\\s\\S]*?)<\\/article>/i);\n  const mainMatch = cleanHtml.match(/<main[^>]*>([\\s\\S]*?)<\\/main>/i);\n  const entryMatch = cleanHtml.match(/<div[^>]*class=[\"'][^\"']*(?:entry-content|post-content|article-content|blog-content)[^\"']*[\"'][^>]*>([\\s\\S]*?)<\\/div>/i);\n  const contentMatch = cleanHtml.match(/<div[^>]*class=[\"'][^\"']*(?:content|post|article|entry)[^\"']*[\"'][^>]*>([\\s\\S]*?)<\\/div>/i);\n  \n  let rawContent = articleMatch ? articleMatch[1] : \n                   mainMatch ? mainMatch[1] : \n                   entryMatch ? entryMatch[1] :\n                   contentMatch ? contentMatch[1] : cleanHtml;\n  \n  // Strip remaining HTML tags and clean up\n  content = rawContent\n    .replace(/<h[1-6][^>]*>/gi, '\\n\\n## ')\n    .replace(/<\\/h[1-6]>/gi, '\\n\\n')\n    .replace(/<p[^>]*>/gi, '\\n')\n    .replace(/<\\/p>/gi, '\\n')\n    .replace(/<br[^>]*>/gi, '\\n')\n    .replace(/<li[^>]*>/gi, '\\n\u2022 ')\n    .replace(/<[^>]+>/g, ' ')\n    .replace(/&nbsp;/g, ' ')\n    .replace(/&amp;/g, '&')\n    .replace(/&lt;/g, '<')\n    .replace(/&gt;/g, '>')\n    .replace(/&quot;/g, '\"')\n    .replace(/&#39;/g, \"'\")\n    .replace(/&hellip;/g, '...')\n    .replace(/&mdash;/g, '\u2014')\n    .replace(/&ndash;/g, '\u2013')\n    .replace(/\\n\\s*\\n\\s*\\n/g, '\\n\\n')\n    .replace(/[ \\t]+/g, ' ')\n    .trim();\n  \n  // Limit content length\n  if (content.length > 12000) {\n    content = content.substring(0, 12000) + '\\n\\n[Content truncated...]';\n  }\n  \n  if (content.length < 100) {\n    throw new Error('Could not extract meaningful content from page');\n  }\n  \n} catch (error) {\n  content = 'Error extracting content: ' + error.message;\n}\n\nreturn {\n  contentType: 'blog',\n  sourceUrl: input['Content Input'].trim(),\n  title: title,\n  author: author,\n  description: description,\n  content: content,\n  targetAudience: input['Target Audience (Optional)'] || '',\n  additionalInstructions: input['Additional Instructions (Optional)'] || '',\n  wordCount: content.split(/\\s+/).length,\n  extractedAt: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "7d037403-97cb-4300-8c19-fbf3ce6e0a78",
      "name": "Extract Video ID",
      "type": "n8n-nodes-base.code",
      "position": [
        -560,
        416
      ],
      "parameters": {
        "jsCode": "// Extract YouTube video ID from URL\nconst input = $('Content Input Form').first().json;\nconst url = input['Content Input'].trim();\n\nlet videoId = '';\n\n// Handle various YouTube URL formats\nconst patterns = [\n  /(?:youtube\\.com\\/watch\\?v=)([^&\\s]+)/,\n  /(?:youtu\\.be\\/)([^?\\s]+)/,\n  /(?:youtube\\.com\\/embed\\/)([^?\\s]+)/,\n  /(?:youtube\\.com\\/v\\/)([^?\\s]+)/,\n  /(?:youtube\\.com\\/shorts\\/)([^?\\s]+)/,\n  /(?:youtube\\.com\\/live\\/)([^?\\s]+)/\n];\n\nfor (const pattern of patterns) {\n  const match = url.match(pattern);\n  if (match) {\n    videoId = match[1].split('&')[0]; // Remove any trailing params\n    break;\n  }\n}\n\nif (!videoId) {\n  throw new Error('Could not extract YouTube video ID from URL: ' + url);\n}\n\nreturn {\n  videoId: videoId,\n  sourceUrl: url,\n  targetAudience: input['Target Audience (Optional)'] || '',\n  additionalInstructions: input['Additional Instructions (Optional)'] || ''\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "1333f5d3-690a-48d7-bad4-ef34d92c204c",
      "name": "Get YouTube Metadata",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -352,
        416
      ],
      "parameters": {
        "url": "=https://www.googleapis.com/youtube/v3/videos",
        "options": {
          "timeout": 10000
        },
        "sendQuery": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "part",
              "value": "snippet,contentDetails,statistics"
            },
            {
              "name": "id",
              "value": "={{ $json.videoId }}"
            }
          ]
        },
        "nodeCredentialType": "youTubeOAuth2Api"
      },
      "credentials": {
        "httpQueryAuth": {
          "name": "<your credential>"
        },
        "youTubeOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "d2ca7488-fe40-46e7-98ee-c2c7154f43a2",
      "name": "Fetch YouTube Page",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -352,
        672
      ],
      "parameters": {
        "url": "=https://www.youtube.com/watch?v={{ $('Extract Video ID').item.json.videoId }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "b80468b8-cf99-4797-91b0-ef3edadf5f31",
      "name": "Process YouTube Data",
      "type": "n8n-nodes-base.code",
      "position": [
        16,
        448
      ],
      "parameters": {
        "jsCode": "// Process YouTube data from metadata API and page scrape\nconst videoIdData = $('Extract Video ID').first().json;\nconst metadataResponse = $('Get YouTube Metadata').first().json;\nconst pageResponse = $('Fetch YouTube Page').first().json;\nconst pageHtml = pageResponse.data || pageResponse.body || '';\n\n// Extract video details from API\nconst video = metadataResponse.items?.[0]?.snippet || {};\nconst stats = metadataResponse.items?.[0]?.statistics || {};\nconst contentDetails = metadataResponse.items?.[0]?.contentDetails || {};\n\nconst title = video.title || 'Unknown Title';\nconst description = video.description || '';\nconst channelTitle = video.channelTitle || '';\nconst publishedAt = video.publishedAt || '';\nconst viewCount = stats.viewCount || '';\nconst likeCount = stats.likeCount || '';\n\n// Try to extract transcript/captions from page\nlet transcript = '';\n\ntry {\n  // Look for captions in the page data\n  const captionsMatch = pageHtml.match(/\"captions\":\\{\"playerCaptionsTracklistRenderer\":\\{\"captionTracks\":\\[([^\\]]+)\\]/);\n  \n  if (captionsMatch) {\n    // Found captions data - extract the URL\n    const captionData = captionsMatch[1];\n    const baseUrlMatch = captionData.match(/\"baseUrl\":\"([^\"]+)\"/);\n    \n    if (baseUrlMatch) {\n      // We found a caption URL - note this for the user\n      transcript = '[Captions available - using video metadata for content]';\n    }\n  }\n  \n  // Also try to find auto-generated transcript in ytInitialPlayerResponse\n  const playerMatch = pageHtml.match(/ytInitialPlayerResponse\\s*=\\s*({.+?});/);\n  if (playerMatch) {\n    try {\n      const playerData = JSON.parse(playerMatch[1]);\n      if (playerData.captions?.playerCaptionsTracklistRenderer?.captionTracks) {\n        transcript = '[Auto-captions detected]';\n      }\n    } catch (e) {\n      // JSON parse failed, continue\n    }\n  }\n} catch (e) {\n  // Transcript extraction failed, continue with metadata\n}\n\n// Build comprehensive content from available data\nlet content = `# ${title}\\n\\n`;\ncontent += `**Channel:** ${channelTitle}\\n`;\nif (publishedAt) {\n  content += `**Published:** ${new Date(publishedAt).toLocaleDateString()}\\n`;\n}\nif (viewCount) {\n  content += `**Views:** ${parseInt(viewCount).toLocaleString()}\\n`;\n}\ncontent += `\\n---\\n\\n`;\n\nif (description) {\n  content += `## Video Description\\n\\n${description}\\n\\n`;\n}\n\nif (transcript) {\n  content += `\\n---\\n\\n*Note: ${transcript}*\\n`;\n}\n\n// Truncate if too long\nif (content.length > 12000) {\n  content = content.substring(0, 12000) + '\\n\\n[Content truncated...]';\n}\n\nreturn {\n  contentType: 'youtube',\n  sourceUrl: videoIdData.sourceUrl,\n  videoId: videoIdData.videoId,\n  title: title,\n  channelTitle: channelTitle,\n  description: description.substring(0, 1000),\n  content: content,\n  viewCount: viewCount,\n  likeCount: likeCount,\n  publishedAt: publishedAt,\n  targetAudience: videoIdData.targetAudience || '',\n  additionalInstructions: videoIdData.additionalInstructions || '',\n  wordCount: content.split(/\\s+/).length,\n  extractedAt: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "4e6a0467-8168-4afa-9bfb-8540e7fe627f",
      "name": "Process Raw Text",
      "type": "n8n-nodes-base.code",
      "position": [
        -384,
        896
      ],
      "parameters": {
        "jsCode": "// Pass raw text through with metadata\nconst input = $('Content Input Form').first().json;\nconst rawText = input['Content Input'].trim();\n\n// Try to extract a title from first line\nlet title = '';\nconst lines = rawText.split('\\n').filter(l => l.trim());\nconst firstLine = lines[0] || '';\n\n// Use first line as title if it's short enough and looks like a title\nif (firstLine.length < 150 && firstLine.length > 3) {\n  // Remove markdown heading markers if present\n  title = firstLine.replace(/^#+\\s*/, '').trim();\n}\n\nlet content = rawText;\n\n// Limit content length\nif (content.length > 12000) {\n  content = content.substring(0, 12000) + '\\n\\n[Content truncated...]';\n}\n\nreturn {\n  contentType: 'raw_text',\n  sourceUrl: '',\n  title: title || 'User Provided Content',\n  description: '',\n  content: content,\n  targetAudience: input['Target Audience (Optional)'] || '',\n  additionalInstructions: input['Additional Instructions (Optional)'] || '',\n  wordCount: content.split(/\\s+/).length,\n  extractedAt: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "7fed92f5-abef-462c-84e0-c814c6d652ed",
      "name": "AI Content Generator",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        400,
        432
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5.1",
          "cachedResultName": "GPT-5.1"
        },
        "options": {
          "maxTokens": 4096,
          "temperature": 0.7
        },
        "messages": {
          "values": [
            {
              "content": "=You are an expert content strategist, copywriter, and social media specialist. Your task is to repurpose content into multiple platform-optimized formats.\n\n## Your Approach\n1. First, deeply analyze the source content to understand:\n   - Core message and key insights\n   - Tone and voice (professional, casual, technical, etc.)\n   - Target audience (if specified, otherwise infer)\n   - Unique angles and quotable moments\n\n2. Then create content that:\n   - Maintains the original value and insights\n   - Adapts to each platform's best practices\n   - Uses hooks and engagement techniques\n   - Provides clear calls-to-action\n\n## Output Requirements\nYou MUST respond with valid JSON in this exact structure:\n\n```json\n{\n  \"analysis\": {\n    \"contentSummary\": \"<2-3 sentence summary of the core message>\",\n    \"detectedTone\": \"<Professional|Casual|Educational|Inspirational|Technical|Conversational|Authoritative>\",\n    \"mainTopics\": [\"<topic1>\", \"<topic2>\", \"<topic3>\"],\n    \"targetAudience\": \"<identified or inferred target audience>\",\n    \"keyMessage\": \"<the single most important takeaway>\"\n  },\n  \n  \"linkedInPosts\": [\n    {\n      \"style\": \"Story-driven\",\n      \"hook\": \"<attention-grabbing first line - make them stop scrolling>\",\n      \"body\": \"<main content with strategic line breaks for readability, 150-200 words>\",\n      \"cta\": \"<call to action - question or engagement prompt>\",\n      \"hashtags\": [\"#relevant\", \"#hashtags\", \"#max5\"]\n    },\n    {\n      \"style\": \"List/Tips format\",\n      \"hook\": \"<hook>\",\n      \"body\": \"<body>\",\n      \"cta\": \"<cta>\",\n      \"hashtags\": []\n    },\n    {\n      \"style\": \"Contrarian/Hot take\",\n      \"hook\": \"<hook>\",\n      \"body\": \"<body>\",\n      \"cta\": \"<cta>\",\n      \"hashtags\": []\n    }\n  ],\n  \n  \"twitterThread\": {\n    \"tweets\": [\n      \"<Tweet 1: Hook - must grab attention, max 280 chars>\",\n      \"<Tweet 2: Context/Problem>\",\n      \"<Tweet 3: Key insight 1>\",\n      \"<Tweet 4: Key insight 2>\",\n      \"<Tweet 5: Key insight 3>\",\n      \"<Tweet 6: Summary/So what?>\",\n      \"<Tweet 7: CTA + link placeholder>\"\n    ],\n    \"altHook\": \"<alternative opening tweet for A/B testing>\"\n  },\n  \n  \"emailNewsletter\": {\n    \"subjectLines\": [\n      \"<option 1 - curiosity-driven>\",\n      \"<option 2 - benefit-focused>\",\n      \"<option 3 - direct/clear>\"\n    ],\n    \"previewText\": \"<50-100 char preview that complements subject>\",\n    \"greeting\": \"<personalized opening>\",\n    \"body\": \"<newsletter body: intro \u2192 main points \u2192 conclusion, conversational tone>\",\n    \"cta\": \"<clear call-to-action>\",\n    \"signOff\": \"<professional sign-off>\"\n  },\n  \n  \"keyTakeaways\": [\n    \"<actionable takeaway 1>\",\n    \"<actionable takeaway 2>\",\n    \"<actionable takeaway 3>\",\n    \"<actionable takeaway 4>\",\n    \"<actionable takeaway 5>\"\n  ],\n  \n  \"quoteCards\": [\n    {\n      \"quote\": \"<impactful, shareable quote - 10-20 words>\",\n      \"context\": \"<brief context for the quote>\"\n    },\n    { \"quote\": \"<quote 2>\", \"context\": \"<context>\" },\n    { \"quote\": \"<quote 3>\", \"context\": \"<context>\" },\n    { \"quote\": \"<quote 4>\", \"context\": \"<context>\" },\n    { \"quote\": \"<quote 5>\", \"context\": \"<context>\" }\n  ],\n  \n  \"shortFormVideoScript\": {\n    \"format\": \"Hook \u2192 Problem \u2192 Solution \u2192 Proof \u2192 CTA\",\n    \"targetLength\": \"60 seconds\",\n    \"sections\": {\n      \"hook\": \"<0-3 sec: pattern interrupt, make them stop scrolling>\",\n      \"problem\": \"<3-12 sec: identify pain point they relate to>\",\n      \"solution\": \"<12-35 sec: present the insight/solution>\",\n      \"proof\": \"<35-50 sec: evidence, example, or story>\",\n      \"cta\": \"<50-60 sec: what should they do next?>\"\n    },\n    \"visualNotes\": \"<suggestions for b-roll, text overlays, or transitions>\"\n  }\n}\n```\n\n## Platform Guidelines\n\n**LinkedIn:**\n- First line is EVERYTHING - make it scroll-stopping\n- Use line breaks for readability (no walls of text)\n- Personal stories and lessons outperform generic advice\n- End with engagement question or clear CTA\n- 5 hashtags max, mix of broad and niche\n\n**Twitter/X:**\n- Each tweet must work standalone AND as part of thread\n- Tweet 1 determines if anyone reads the rest\n- Use numbers, contrarian takes, or curiosity gaps\n- Keep most tweets under 200 chars for retweets\n- End with clear CTA\n\n**Email:**\n- Subject line is 80% of the battle\n- Preview text should complement, not repeat subject\n- Write like you're emailing a smart friend\n- One clear CTA per email\n\n**Quote Cards:**\n- Must work WITHOUT context\n- Emotional or thought-provoking\n- Easy to read at a glance\n- No jargon\n\n**Video Script:**\n- Hook in first 3 seconds or they scroll\n- Speak directly to viewer (\"you\")\n- One idea per video\n- Clear, actionable ending"
            },
            {
              "content": "=Repurpose this content into multiple platform-optimized formats:\n\n---\n\n**Source Type:** {{ $json.contentType }}\n**Title:** {{ $json.title }}\n{{ $json.sourceUrl ? '**Source URL:** ' + $json.sourceUrl : '' }}\n{{ $json.channelTitle ? '**Channel:** ' + $json.channelTitle : '' }}\n{{ $json.author ? '**Author:** ' + $json.author : '' }}\n**Word Count:** ~{{ $json.wordCount }} words\n\n{{ $json.targetAudience ? '**Target Audience:** ' + $json.targetAudience : '' }}\n{{ $json.additionalInstructions ? '**Special Instructions:** ' + $json.additionalInstructions : '' }}\n\n---\n\n## CONTENT TO REPURPOSE:\n\n{{ $json.content }}\n\n---\n\nGenerate all content variations. Respond with valid JSON only."
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "a94b8439-4645-4270-851a-ccecabba2105",
      "name": "Format Document Content",
      "type": "n8n-nodes-base.code",
      "position": [
        880,
        432
      ],
      "parameters": {
        "jsCode": "// Parse AI response and build formatted Google Doc content\n\n// 1) Figure out where the normalized source content came from\nlet sourceData = null;\n\nfunction safeGetNodeFirstJson(nodeName) {\n  try {\n    return $(nodeName).first().json;\n  } catch (e) {\n    return null;\n  }\n}\n\n// Try blog path\nsourceData = safeGetNodeFirstJson('Extract Blog Content');\n\n// If not blog, try YouTube path\nif (!sourceData || !sourceData.contentType) {\n  sourceData = safeGetNodeFirstJson('Process YouTube Data');\n}\n\n// If not YouTube, try raw text path\nif (!sourceData || !sourceData.contentType) {\n  sourceData = safeGetNodeFirstJson('Process Raw Text');\n}\n\nif (!sourceData) {\n  throw new Error('Could not determine source content (blog / YouTube / raw text). Make sure one of the extraction nodes ran successfully.');\n}\n\n// 2) Get AI response from previous node\nconst aiResponse = $input.first().json;\n\n// 3) Parse AI JSON safely\nlet content;\ntry {\n  if (typeof aiResponse.message?.content === 'string') {\n    // Clean any markdown code blocks\n    let jsonStr = aiResponse.message.content\n      .replace(/```json\\n?/g, '')\n      .replace(/```\\n?/g, '')\n      .trim();\n    content = JSON.parse(jsonStr);\n  } else {\n    content = aiResponse.message?.content || aiResponse;\n  }\n} catch (e) {\n  throw new Error(\n    'Failed to parse AI response: ' +\n      e.message +\n      '\\n\\nRaw response: ' +\n      JSON.stringify(aiResponse).substring(0, 500)\n  );\n}\n\n// 4) Build Google Doc content\nconst cleanTitle = sourceData.title || 'Repurposed Content';\nconst docTitle =\n  `Repurposed: ${cleanTitle.substring(0, 60)}` +\n  (cleanTitle.length > 60 ? '...' : '');\n\nlet doc = '';\n\n// Header\ndoc += `# ${cleanTitle}\\n\\n`;\ndoc += `\ud83d\udcc5 Generated: ${new Date().toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric',\n})}\\n`;\n\ndoc += `\ud83d\udcdd Source: ${\n  sourceData.contentType === 'youtube'\n    ? 'YouTube Video'\n    : sourceData.contentType === 'blog'\n    ? 'Blog Post'\n    : 'Raw Text'\n}\\n`;\n\nif (sourceData.sourceUrl) {\n  doc += `\ud83d\udd17 URL: ${sourceData.sourceUrl}\\n`;\n}\ndoc += `\\n---\\n\\n`;\n\n// Analysis Summary\nif (content.analysis) {\n  doc += `## \ud83d\udcca Content Analysis\\n\\n`;\n  doc += `**Summary:** ${content.analysis.contentSummary || 'N/A'}\\n\\n`;\n  doc += `**Detected Tone:** ${content.analysis.detectedTone || 'N/A'}\\n`;\n  doc += `**Target Audience:** ${content.analysis.targetAudience || 'N/A'}\\n`;\n  doc += `**Key Message:** ${content.analysis.keyMessage || 'N/A'}\\n`;\n  doc += `**Main Topics:** ${(content.analysis.mainTopics || []).join(', ')}\\n\\n`;\n  doc += `---\\n\\n`;\n}\n\n// LinkedIn Posts\ndoc += `## \ud83d\udcbc LinkedIn Posts\\n\\n`;\nif (content.linkedInPosts && content.linkedInPosts.length > 0) {\n  content.linkedInPosts.forEach((post, i) => {\n    doc += `### Option ${i + 1}: ${post.style || 'Variation ' + (i + 1)}\\n\\n`;\n    doc += `**HOOK:**\\n${post.hook}\\n\\n`;\n    doc += `**BODY:**\\n${post.body}\\n\\n`;\n    doc += `**CTA:**\\n${post.cta}\\n\\n`;\n    if (post.hashtags && post.hashtags.length > 0) {\n      doc += `**Hashtags:** ${post.hashtags.join(' ')}\\n\\n`;\n    }\n    doc += `---\\n\\n`;\n  });\n} else {\n  doc += `*No LinkedIn posts generated*\\n\\n`;\n}\n\n// Twitter Thread\ndoc += `## \ud83d\udc26 Twitter/X Thread\\n\\n`;\nif (content.twitterThread?.tweets && content.twitterThread.tweets.length > 0) {\n  content.twitterThread.tweets.forEach((tweet, i) => {\n    doc += `**${i + 1}/${content.twitterThread.tweets.length}**\\n${tweet}\\n\\n`;\n  });\n  if (content.twitterThread.altHook) {\n    doc += `**\ud83d\udd04 Alternative Hook (for A/B testing):**\\n${content.twitterThread.altHook}\\n\\n`;\n  }\n} else {\n  doc += `*No Twitter thread generated*\\n\\n`;\n}\ndoc += `---\\n\\n`;\n\n// Email Newsletter\ndoc += `## \ud83d\udce7 Email Newsletter\\n\\n`;\nif (content.emailNewsletter) {\n  const email = content.emailNewsletter;\n  doc += `### Subject Line Options\\n`;\n  (email.subjectLines || []).forEach((subj, i) => {\n    doc += `${i + 1}. ${subj}\\n`;\n  });\n  doc += `\\n**Preview Text:** ${email.previewText || ''}\\n\\n`;\n  doc += `### Email Body\\n\\n`;\n  if (email.greeting) doc += `${email.greeting}\\n\\n`;\n  doc += `${email.body || ''}\\n\\n`;\n  if (email.cta) doc += `**CTA:** ${email.cta}\\n\\n`;\n  if (email.signOff) doc += `${email.signOff}\\n\\n`;\n} else {\n  doc += `*No email newsletter generated*\\n\\n`;\n}\ndoc += `---\\n\\n`;\n\n// Key Takeaways\ndoc += `## \ud83c\udfaf Key Takeaways\\n\\n`;\nif (content.keyTakeaways && content.keyTakeaways.length > 0) {\n  content.keyTakeaways.forEach((takeaway, i) => {\n    doc += `${i + 1}. ${takeaway}\\n`;\n  });\n} else {\n  doc += `*No takeaways generated*\\n`;\n}\ndoc += `\\n---\\n\\n`;\n\n// Quote Cards\ndoc += `## \ud83d\udcac Quote Cards\\n\\n`;\ndoc += `*Ready for social media graphics*\\n\\n`;\nif (content.quoteCards && content.quoteCards.length > 0) {\n  content.quoteCards.forEach((item) => {\n    const quote = typeof item === 'string' ? item : item.quote;\n    const context = typeof item === 'object' ? item.context : '';\n    doc += `> \"${quote}\"\\n`;\n    if (context) doc += `> *\u2014 ${context}*\\n`;\n    doc += `\\n`;\n  });\n} else {\n  doc += `*No quotes generated*\\n`;\n}\ndoc += `---\\n\\n`;\n\n// Video Script\ndoc += `## \ud83c\udfac Short-Form Video Script\\n\\n`;\nif (content.shortFormVideoScript) {\n  const script = content.shortFormVideoScript;\n  doc += `**Format:** ${script.format || 'Hook \u2192 Problem \u2192 Solution \u2192 CTA'}\\n`;\n  doc += `**Target Length:** ${script.targetLength || '60 seconds'}\\n\\n`;\n\n  if (script.sections) {\n    doc += `### \ud83c\udfa3 HOOK (0-3 sec)\\n${script.sections.hook || ''}\\n\\n`;\n    doc += `### \u2753 PROBLEM (3-12 sec)\\n${script.sections.problem || ''}\\n\\n`;\n    doc += `### \ud83d\udca1 SOLUTION (12-35 sec)\\n${script.sections.solution || ''}\\n\\n`;\n    doc += `### \u2705 PROOF (35-50 sec)\\n${script.sections.proof || ''}\\n\\n`;\n    doc += `### \ud83d\udc49 CTA (50-60 sec)\\n${script.sections.cta || ''}\\n\\n`;\n  }\n\n  if (script.visualNotes) {\n    doc += `### \ud83c\udfa5 Visual Notes\\n${script.visualNotes}\\n\\n`;\n  }\n} else {\n  doc += `*No video script generated*\\n`;\n}\n\ndoc += `---\\n\\n`;\ndoc += `*Generated by AI Content Repurposing Engine*\\n`;\ndoc += `*Created by [Agentical AI](https://agenticalai.com)*`;\n\n// 5) Return data for Google Docs node\nreturn {\n  docTitle: docTitle,\n  docContent: doc,\n  sourceTitle: sourceData.title,\n  sourceType: sourceData.contentType,\n  sourceUrl: sourceData.sourceUrl || null,\n  analysis: content.analysis || {},\n  generatedAt: new Date().toISOString(),\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "12f82b77-ccac-4d0b-8332-65f5f332652b",
      "name": "Create Google Doc",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        1072,
        432
      ],
      "parameters": {
        "title": "={{ $json.docTitle }}",
        "folderId": "default"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "72e308c8-ac7a-4a07-9319-8a5d035e1c5d",
      "name": "Write Content",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        1280,
        432
      ],
      "parameters": {
        "actionsUi": {
          "actionFields": [
            {
              "text": "={{ $('Format Document Content').item.json.docContent }}",
              "action": "insert"
            }
          ]
        },
        "operation": "update",
        "documentURL": "={{ $json.id }}"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "f46807fa-55c1-41eb-9524-6939d753d36c",
      "name": "Prepare Response",
      "type": "n8n-nodes-base.code",
      "position": [
        1488,
        432
      ],
      "parameters": {
        "jsCode": "// Prepare final response data\nconst docData = $('Write Content').first().json;\nconst prepData = $('Format Document Content').first().json;\n\nconst docUrl = `https://docs.google.com/document/d/${docData.documentId}/edit`;\n\nreturn {\n  success: true,\n  documentUrl: docUrl,\n  documentId: docData.documentId,\n  documentTitle: prepData.docTitle,\n  sourceType: prepData.sourceType,\n  sourceTitle: prepData.sourceTitle,\n  sourceUrl: prepData.sourceUrl,\n  analysis: prepData.analysis,\n  generatedAt: prepData.generatedAt\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "a129239a-e376-4e35-a036-850e6f8b6013",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        1696,
        432
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "00d35c7e-ac6d-43b4-abbb-66de177fc32a",
              "name": "documentUrl",
              "type": "string",
              "value": "={{ $json.documentUrl }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "connections": {
    "Is Blog URL?": {
      "main": [
        [
          {
            "node": "Fetch Blog Page",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Is YouTube URL?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Content": {
      "main": [
        [
          {
            "node": "Prepare Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Blog Page": {
      "main": [
        [
          {
            "node": "Extract Blog Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is YouTube URL?": {
      "main": [
        [
          {
            "node": "Extract Video ID",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Process Raw Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Video ID": {
      "main": [
        [
          {
            "node": "Get YouTube Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Response": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Raw Text": {
      "main": [
        [
          {
            "node": "AI Content Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Google Doc": {
      "main": [
        [
          {
            "node": "Write Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Content Input Form": {
      "main": [
        [
          {
            "node": "Is Blog URL?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch YouTube Page": {
      "main": [
        [
          {
            "node": "Process YouTube Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Content Generator": {
      "main": [
        [
          {
            "node": "Format Document Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Blog Content": {
      "main": [
        [
          {
            "node": "AI Content Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get YouTube Metadata": {
      "main": [
        [
          {
            "node": "Fetch YouTube Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process YouTube Data": {
      "main": [
        [
          {
            "node": "AI Content Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Document Content": {
      "main": [
        [
          {
            "node": "Create Google Doc",
            "type": "main",
            "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

Transform blog posts, YouTube videos, or any text into LinkedIn posts, Twitter threads, email newsletters, and more with GPT-5.1 Content creators who want to maximize reach from every piece of content Marketing teams repurposing long-form content for social media Founders &…

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

In this tutorial, I’ll walk you through a step-by-step N8N workflow that combines the power of OpenAI and Claude AI to generate professional, ready-to-use lead magnet plans for any niche.

Form Trigger, OpenAI, Google Docs +2
AI & RAG

This workflow is perfect for content marketers, bloggers, SEO professionals, and virtual assistants who need to transform keyword research into complete blog posts without spending hours writing and f

Form Trigger, HTTP Request, OpenAI +1
AI & RAG

Note: Now includes an Apify alternative for Rapid API (Some users can't create new accounts on Rapid API, so I have added an alternative for you. But immediately you are able to get access to Rapid AP

Form Trigger, Google Sheets Trigger, OpenAI +2
AI & RAG

This system automates LinkedIn lead generation and enrichment in six clear stages: Lead Collection (via Apollo.io) Automatically pulls leads based on keywords, roles, or industries using Apollo’s API.

Form Trigger, OpenAI, Google Sheets Trigger +2
AI & RAG

This workflow contains community nodes that are only compatible with the self-hosted version of n8n.

Airtable, OpenAI, Form Trigger +3