Blog / Tutorials / N8N Automation: Convert YouTube Videos to WordPress Blog Posts in Minutes
Tutorials 9 min read February 05, 2026

N8N Automation: Convert YouTube Videos to WordPress Blog Posts in Minutes

Founder
N8N Automation: Convert YouTube Videos to WordPress Blog Posts in Minutes

What if every YouTube video you published automatically became a fully-formatted, SEO-optimized WordPress blog post—without you lifting a finger? With N8N automation and Scriptube's transcript API, this isn't fantasy. It's a 15-minute setup that content creators are using to 10x their output.

The Content Repurposing Bottleneck Every Creator Faces

You just uploaded a killer 20-minute YouTube video. Great content, solid editing, valuable insights. But here's the problem: 90% of your potential audience will never see it.

Why? Because not everyone searches YouTube. Millions search Google. They read blogs. They want text they can skim, copy, reference, and bookmark. Your video content is trapped in a single format while competitors are everywhere.

The traditional solution? Manual transcription and rewriting. Content creators report spending 2-4 hours converting each video to a blog post. That's 8-16 hours per week just on repurposing—time that should go toward creating new content.

The real cost isn't just time. It's the articles you never published, the SEO traffic you never captured, and the audience segments you never reached.

The Automated Solution: YouTube → Transcript → AI → WordPress

Here's the automation pipeline that changes everything:

  1. Trigger: New video published to your YouTube channel
  2. Extract: Scriptube API pulls the complete transcript with timestamps
  3. Transform: GPT-4 converts spoken transcript into polished article format
  4. Enhance: AI adds headers, internal links, meta descriptions, and CTAs
  5. Publish: WordPress REST API creates draft or publishes directly
  6. Notify: Slack/email confirmation with preview link

Total time from video upload to blog post? Under 3 minutes. Zero manual work.

Workflow diagram showing YouTube to WordPress automation pipeline

Complete N8N Workflow: Copy, Paste, Customize

Here's the production-ready N8N workflow. Import this JSON directly into your N8N instance:

{
  "name": "YouTube to WordPress Blog Automation",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [{"field": "hours", "hoursInterval": 1}]
        }
      },
      "name": "Check for New Videos",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [250, 300]
    },
    {
      "parameters": {
        "resource": "video",
        "operation": "getAll",
        "channelId": "={{ $json.youtube_channel_id }}",
        "limit": 5,
        "options": {
          "order": "date"
        }
      },
      "name": "Get Latest Videos",
      "type": "n8n-nodes-base.youTube",
      "position": [450, 300]
    },
    {
      "parameters": {
        "url": "https://api.scriptube.app/v1/transcript",
        "method": "POST",
        "authentication": "predefinedCredentialType",
        "body": {
          "video_url": "={{ 'https://youtube.com/watch?v=' + $json.id.videoId }}",
          "format": "text",
          "include_timestamps": false
        }
      },
      "name": "Get Transcript via Scriptube",
      "type": "n8n-nodes-base.httpRequest",
      "position": [650, 300]
    },
    {
      "parameters": {
        "model": "gpt-4o",
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are an expert blog writer. Convert the following video transcript into a well-structured blog post. Requirements:\n- Create an engaging title (include main keyword)\n- Write a compelling meta description (150-160 chars)\n- Add H2 headers for each major section\n- Include bullet points for lists\n- Add a call-to-action at the end\n- Maintain the original voice and key insights\n- Format in clean HTML"
            },
            {
              "role": "user", 
              "content": "Video Title: {{ $node['Get Latest Videos'].json.snippet.title }}\n\nTranscript:\n{{ $node['Get Transcript via Scriptube'].json.transcript }}"
            }
          ]
        }
      },
      "name": "Transform with GPT-4",
      "type": "n8n-nodes-base.openAi",
      "position": [850, 300]
    },
    {
      "parameters": {
        "resource": "post",
        "operation": "create",
        "title": "={{ $json.choices[0].message.content.match(/(.*?)<\/title>/)?.[1] }}",
        "content": "={{ $json.choices[0].message.content }}",
        "status": "draft",
        "additionalFields": {
          "categories": ["YouTube Repurposed"],
          "featured_media": "={{ $node['Get Latest Videos'].json.snippet.thumbnails.high.url }}"
        }
      },
      "name": "Create WordPress Post",
      "type": "n8n-nodes-base.wordpress",
      "position": [1050, 300]
    },
    {
      "parameters": {
        "channel": "#content-published",
        "text": "📝 New blog post created from YouTube video!\n\n*{{ $node['Get Latest Videos'].json.snippet.title }}*\n\nPreview: {{ $json.link }}\nStatus: Draft (review before publishing)"
      },
      "name": "Slack Notification",
      "type": "n8n-nodes-base.slack",
      "position": [1250, 300]
    }
  ],
  "connections": {
    "Check for New Videos": {"main": [[{"node": "Get Latest Videos", "type": "main", "index": 0}]]},
    "Get Latest Videos": {"main": [[{"node": "Get Transcript via Scriptube", "type": "main", "index": 0}]]},
    "Get Transcript via Scriptube": {"main": [[{"node": "Transform with GPT-4", "type": "main", "index": 0}]]},
    "Transform with GPT-4": {"main": [[{"node": "Create WordPress Post", "type": "main", "index": 0}]]},
    "Create WordPress Post": {"main": [[{"node": "Slack Notification", "type": "main", "index": 0}]]}
  }
}</code></pre>
  
  <h3>Setup Checklist</h3>
  
  <ul>
    <li>✅ Connect your YouTube API credentials in N8N</li>
    <li>✅ Add your Scriptube API key (get it free at <a href="/ui/signup">scriptube.app/signup</a>)</li>
    <li>✅ Configure OpenAI API for GPT-4 access</li>
    <li>✅ Set up WordPress REST API authentication (Application Password recommended)</li>
    <li>✅ Optional: Add Slack webhook for notifications</li>
  </ul>
  
  <h2 id="ai-enhancement">AI Enhancement: Turning Raw Transcripts into Polished Articles</h2>
  
  <p>Raw video transcripts make terrible blog posts. They're conversational, repetitive, and lack structure. The magic is in the AI transformation layer.</p>
  
  <p>Here's what GPT-4 does in the workflow:</p>
  
  <table>
    <thead>
      <tr><th>Input (Transcript)</th><th>Output (Blog Post)</th></tr>
    </thead>
    <tbody>
      <tr><td>"So, um, today we're gonna talk about..."</td><td>Clean intro paragraph</td></tr>
      <tr><td>Verbal tangents and "you know"s</td><td>Focused, scannable content</td></tr>
      <tr><td>Spoken instructions</td><td>Numbered step-by-step lists</td></tr>
      <tr><td>Casual mentions</td><td>Proper H2/H3 section headers</td></tr>
      <tr><td>No links</td><td>Contextual internal links added</td></tr>
    </tbody>
  </table>
  
  <p><strong>Pro tip:</strong> Customize the system prompt to match your brand voice. Add examples of your best posts as few-shot examples for consistent output.</p>
  
  <h2 id="advanced">Advanced: Multi-Language Publishing with Scriptube Translation</h2>
  
  <p>Want to publish in Spanish, German, and French simultaneously? Scriptube's translation feature makes it simple:</p>
  
  <pre><code class="language-json">{
  "video_url": "https://youtube.com/watch?v=...",
  "translate_to": ["es", "de", "fr"],
  "format": "text"
}</code></pre>
  
  <p>Each translated transcript can flow into a separate WordPress site or multisite language installation. One video → four blog posts in four languages.</p>
  
  <p>For content creators targeting international audiences, this workflow generates:</p>
  <ul>
    <li>🇺🇸 English original article</li>
    <li>🇪🇸 Spanish version for Latin American markets</li>
    <li>🇩🇪 German version for DACH region</li>
    <li>🇫🇷 French version for European French speakers</li>
  </ul>
  
  <h2 id="audio">Bonus: Generate Audio Versions with ElevenLabs</h2>
  
  <p>Why stop at text? Add an ElevenLabs node to create audio versions of your blog posts:</p>
  
  <pre><code class="language-json">{
  "name": "Generate Audio Article",
  "type": "n8n-nodes-base.httpRequest",
  "parameters": {
    "url": "https://api.elevenlabs.io/v1/text-to-speech/{{ $json.voice_id }}",
    "method": "POST",
    "body": {
      "text": "{{ $node['Transform with GPT-4'].json.article_text }}",
      "model_id": "eleven_multilingual_v2"
    }
  }
}</code></pre>
  
  <p>Upload the audio to your WordPress media library and embed it at the top of each post. Now readers can listen while commuting—accessibility and convenience in one automation.</p>
  
  <h2 id="roi">ROI Calculator: What This Automation Saves You</h2>
  
  <p>Let's do the math for a creator publishing 3 videos per week:</p>
  
  <table>
    <thead>
      <tr><th>Metric</th><th>Manual Process</th><th>Automated</th></tr>
    </thead>
    <tbody>
      <tr><td>Time per article</td><td>3 hours</td><td>0 hours</td></tr>
      <tr><td>Weekly time spent</td><td>9 hours</td><td>15 minutes (review)</td></tr>
      <tr><td>Monthly time spent</td><td>36 hours</td><td>1 hour</td></tr>
      <tr><td>Annual time saved</td><td>—</td><td><strong>420 hours</strong></td></tr>
      <tr><td>Value @ $50/hr</td><td>—</td><td><strong>$21,000/year</strong></td></tr>
    </tbody>
  </table>
  
  <p>Plus the indirect ROI:</p>
  <ul>
    <li>📈 <strong>3x more indexed pages</strong> — More content means more Google entry points</li>
    <li>🔍 <strong>Long-tail SEO capture</strong> — Text ranks for queries video doesn't</li>
    <li>♿ <strong>Accessibility compliance</strong> — Deaf/HoH audiences can access your content</li>
    <li>📚 <strong>Content archive</strong> — Searchable, linkable knowledge base</li>
  </ul>
  
  <div class="cta-box" style="background: #f0f9ff; padding: 24px; border-radius: 8px; margin: 32px 0;">
    <h3>Ready to Automate Your Content Pipeline?</h3>
    <p>Get started with Scriptube's transcript API—free tier includes 50 transcripts/month. Perfect for testing this workflow.</p>
    <p><a href="/ui/signup" style="background: #2563eb; color: white; padding: 12px 24px; border-radius: 6px; text-decoration: none; display: inline-block;">Start Free with Scriptube →</a></p>
  </div>
  
  <h2>Common Questions</h2>
  
  <h3>Does this work with private or unlisted videos?</h3>
  <p>Yes! Scriptube can extract transcripts from unlisted videos using the direct URL. Private videos require the owner's authorization.</p>
  
  <h3>What about videos without captions?</h3>
  <p>Scriptube uses YouTube's auto-generated captions when available. For videos without any captions, you can use Scriptube's Whisper-based transcription (available on Pro plan).</p>
  
  <h3>Can I customize the AI prompt for my niche?</h3>
  <p>Absolutely. The GPT-4 system prompt is fully customizable. Add your brand guidelines, preferred formatting, required CTAs, and even example posts for consistent output.</p>
  
  <h2>Keep Reading</h2>
  <ul class="related">
    <li><a href="/blog/n8n-youtube-transcript-notion-pipeline">N8N: Auto-Import YouTube Transcripts to Notion Database</a></li>
    <li><a href="/blog/n8n-transcript-gpt-summarization">N8N: AI-Summarize YouTube Transcripts with GPT-4</a></li>
    <li><a href="/blog/n8n-multilingual-transcript-pipeline">N8N: Auto-Translate Transcripts to 10+ Languages</a></li>
    <li><a href="/blog/podcast-repurposing-youtube-transcripts">Turn YouTube Videos into Podcast Episodes</a></li>
  </ul>
</article>
  </div>

  <!-- CTA Banner -->
  <div class="card" style="background: linear-gradient(135deg, var(--accent), #818cf8); margin: 48px 0; padding: 32px; text-align: center;">
    <h3 style="font-size: 24px; font-weight: 700; margin-bottom: 12px; color: white;">Try Scriptube Free</h3>
    <p style="color: rgba(255,255,255,0.9); margin-bottom: 24px;">Extract YouTube transcripts instantly. No credit card required.</p>
    <a href="/ui/signup" class="btn" style="background: white; color: var(--accent); font-weight: 600;">Get Started</a>
  </div>

  <!-- Related posts -->
  
  <div style="margin-top: 64px; padding-top: 32px; border-top: 1px solid var(--border);">
    <h3 style="font-size: 24px; font-weight: 700; margin-bottom: 24px;">Related Articles</h3>
    <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px;">
      
      <a href="/blog/n8n-transcript-gpt-summarization" style="text-decoration: none;">
        <div class="card" style="height: 100%;">
          <div style="padding: 20px;">
            <span class="badge badge-done" style="margin-bottom: 12px;">Tutorials</span>
            <h4 style="font-size: 16px; font-weight: 600; margin-bottom: 8px; color: var(--text);">N8N + GPT-4: Build an Automated YouTube Transcript Summarization Pipeline</h4>
            <p style="color: var(--text-dim); font-size: 14px; line-height: 1.6;">
  
  
  N8N + GPT-4: Build an Automated YouTube Transcript Summarization Pipeline
  
  What if every YouTube video you needed could be distilled into a...</p>
          </div>
        </div>
      </a>
      
      <a href="/blog/chatgpt-youtube-transcript-qa" style="text-decoration: none;">
        <div class="card" style="height: 100%;">
          <div style="padding: 20px;">
            <span class="badge badge-done" style="margin-bottom: 12px;">Tutorials</span>
            <h4 style="font-size: 16px; font-weight: 600; margin-bottom: 8px; color: var(--text);">Build a ChatGPT Q&A Bot for Any YouTube Channel Using Transcripts</h4>
            <p style="color: var(--text-dim); font-size: 14px; line-height: 1.6;">
  
  
  What if you could ask any question about a YouTube channel's entire library—and get instant, accurate answers? With RAG (Retrieval Augmented...</p>
          </div>
        </div>
      </a>
      
      <a href="/blog/n8n-youtube-transcript-notion-pipeline" style="text-decoration: none;">
        <div class="card" style="height: 100%;">
          <div style="padding: 20px;">
            <span class="badge badge-done" style="margin-bottom: 12px;">Tutorials</span>
            <h4 style="font-size: 16px; font-weight: 600; margin-bottom: 8px; color: var(--text);">N8N: Auto-Import YouTube Transcripts to Notion Database</h4>
            <p style="color: var(--text-dim); font-size: 14px; line-height: 1.6;">
  
  
  N8N: Auto-Import YouTube Transcripts to Notion Database
  By Mihail Lungu, Founder | February 5, 2026 | 9 min read
  
  What if every YouTube video...</p>
          </div>
        </div>
      </a>
      
    </div>
  </div>
  
</div>

<style>
.article-content h2 {
  font-size: 28px;
  font-weight: 700;
  margin: 32px 0 16px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.article-content h3 {
  font-size: 22px;
  font-weight: 600;
  margin: 24px 0 12px;
}
.article-content {
  overflow-wrap: break-word;
  word-break: break-word;
  max-width: 100%;
}
.article-content p {
  margin-bottom: 16px;
}
.article-content ul, .article-content ol {
  margin: 16px 0;
  padding-left: 32px;
}
.article-content li {
  margin-bottom: 8px;
}
.article-content code {
  background: var(--surface2);
  padding: 2px 6px;
  border-radius: 4px;
  font-family: 'Consolas', 'Monaco', monospace;
  font-size: 14px;
}
.article-content pre {
  background: var(--surface2);
  padding: 16px;
  border-radius: 6px;
  overflow-x: auto;
  margin: 16px 0;
}
.article-content pre code {
  background: none;
  padding: 0;
}
.article-content a {
  color: var(--accent);
  text-decoration: underline;
}
.article-content a:hover {
  color: var(--accent-hover);
}
.article-content blockquote {
  border-left: 4px solid var(--accent);
  padding-left: 20px;
  margin: 20px 0;
  color: var(--text-dim);
  font-style: italic;
}
</style>

  </main>

  <!-- Footer -->
  <footer style="margin-top: 40px; padding: 24px; border-top: 1px solid var(--border); background: var(--surface); text-align: center;">
    <div style="display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap; color: var(--text-dim); font-size: 13px;">
      <span>© 2024-2026 Scriptube</span>
      <span>·</span>
      <a href="/ui/pricing" style="color: var(--text-dim);">Pricing</a>
      <span>·</span>
      <a href="/blog" style="color: var(--text-dim);">Blog</a>
      <span>·</span>
      <a href="/ui/feedback" style="color: var(--text-dim);">Feedback</a>
      
      <span>·</span>
      <span style="display: inline-flex; align-items: center; gap: 4px;">
        Powered by
        <a href="https://elevenlabs.io" target="_blank" rel="noopener" style="color: var(--accent); display: inline-flex; align-items: center; gap: 4px;">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M7 4h2v16H7V4zm8 0h2v16h-2V4z"/></svg>
          ElevenLabs
        </a>
      </span>
      
    </div>
  </footer>

  <script>
    // Mobile menu toggle
    function toggleMobileMenu() {
      const navLinks = document.getElementById('navbar-links');
      const overlay = document.getElementById('nav-overlay');
      const hamburger = document.querySelector('.hamburger');
      if (navLinks && overlay && hamburger) {
        navLinks.classList.toggle('active');
        overlay.classList.toggle('active');
        hamburger.classList.toggle('active');
      }
    }

    // Dropdown toggle
    function toggleDropdown(event) {
      event.preventDefault();
      event.stopPropagation();
      const dropdown = event.target.nextElementSibling;
      if (dropdown) {
        dropdown.classList.toggle('show');
      }
    }

    // User dropdown toggle
    function toggleUserDropdown(event) {
      event.preventDefault();
      event.stopPropagation();
      const dropdown = event.target.closest('.user-dropdown');
      if (dropdown) {
        dropdown.classList.toggle('open');
      }
    }

    // Close dropdowns when clicking outside
    document.addEventListener('click', function(event) {
      const dropdowns = document.querySelectorAll('.dropdown-menu.show');
      dropdowns.forEach(function(dropdown) {
        dropdown.classList.remove('show');
      });
      // Close user dropdown
      const userDropdown = document.querySelector('.user-dropdown.open');
      if (userDropdown && !userDropdown.contains(event.target)) {
        userDropdown.classList.remove('open');
      }
    });

    // Toast notification system
    function showToast(message, type = 'info') {
      const container = document.getElementById('toast-container');
      const toast = document.createElement('div');
      toast.className = `toast ${type}`;

      // Icon based on type
      const icons = {
        success: '✅',
        error: '❌',
        warning: '⚠️',
        info: 'ℹ️'
      };

      // Duration based on type (milliseconds)
      const durations = {
        success: 4000,  // 4 seconds
        error: 8000,    // 8 seconds
        warning: 6000,  // 6 seconds
        info: 4000      // 4 seconds
      };

      toast.innerHTML = `
        <div class="toast-icon">${icons[type] || icons.info}</div>
        <div class="toast-content">${message}</div>
        <button class="toast-close" onclick="closeToast(this)" aria-label="Close">×</button>
      `;

      container.appendChild(toast);

      // Auto-dismiss based on type duration
      const duration = durations[type] || 5000;
      setTimeout(() => {
        closeToast(toast.querySelector('.toast-close'));
      }, duration);
    }

    function closeToast(button) {
      const toast = button.parentElement || button;
      toast.classList.add('hiding');
      setTimeout(() => {
        toast.remove();
      }, 300);
    }

    // Custom confirm modal (replaces ugly browser confirm)
    function showConfirm(message, options = {}) {
      return new Promise((resolve) => {
        const title = options.title || 'Confirm';
        const confirmText = options.confirmText || 'Confirm';
        const cancelText = options.cancelText || 'Cancel';
        const type = options.type || 'warning'; // warning, danger, info

        const overlay = document.createElement('div');
        overlay.className = 'confirm-overlay';
        overlay.innerHTML = `
          <div class="confirm-modal">
            <div class="confirm-header">
              <span class="confirm-icon">${type === 'danger' ? '⚠️' : type === 'info' ? 'ℹ️' : '🔄'}</span>
              <h3>${title}</h3>
            </div>
            <div class="confirm-body">
              <p>${message}</p>
            </div>
            <div class="confirm-footer">
              <button class="btn btn-outline confirm-cancel">${cancelText}</button>
              <button class="btn ${type === 'danger' ? 'btn-danger' : 'btn-primary'} confirm-ok">${confirmText}</button>
            </div>
          </div>
        `;

        document.body.appendChild(overlay);

        // Animate in
        requestAnimationFrame(() => {
          overlay.classList.add('show');
        });

        const closeModal = (result) => {
          overlay.classList.remove('show');
          setTimeout(() => overlay.remove(), 200);
          resolve(result);
        };

        overlay.querySelector('.confirm-cancel').onclick = () => closeModal(false);
        overlay.querySelector('.confirm-ok').onclick = () => closeModal(true);
        overlay.onclick = (e) => { if (e.target === overlay) closeModal(false); };

        // Focus the confirm button
        overlay.querySelector('.confirm-ok').focus();

        // ESC to cancel
        const escHandler = (e) => {
          if (e.key === 'Escape') {
            document.removeEventListener('keydown', escHandler);
            closeModal(false);
          }
        };
        document.addEventListener('keydown', escHandler);
      });
    }

    // Check URL params for flash messages
    window.addEventListener('DOMContentLoaded', () => {
      const params = new URLSearchParams(window.location.search);
      if (params.get('success')) {
        showToast(params.get('success'), 'success');
        // Clean URL
        const url = new URL(window.location);
        url.searchParams.delete('success');
        window.history.replaceState({}, '', url);
      }
      if (params.get('error')) {
        showToast(params.get('error'), 'error');
        const url = new URL(window.location);
        url.searchParams.delete('error');
        window.history.replaceState({}, '', url);
      }
      if (params.get('warning')) {
        showToast(params.get('warning'), 'warning');
        const url = new URL(window.location);
        url.searchParams.delete('warning');
        window.history.replaceState({}, '', url);
      }
      if (params.get('info')) {
        showToast(params.get('info'), 'info');
        const url = new URL(window.location);
        url.searchParams.delete('info');
        window.history.replaceState({}, '', url);
      }
    });

    // Handle internal link clicks - show toast on 404 instead of navigating
    document.addEventListener('click', async function(e) {
      const link = e.target.closest('a');
      if (!link) return;
      
      const href = link.getAttribute('href');
      if (!href || href.startsWith('http') || href.startsWith('#') || href.startsWith('mailto:')) return;
      
      // Check if it's an internal link
      if (href.startsWith('/')) {
        e.preventDefault();
        try {
          const response = await fetch(href, { method: 'HEAD' });
          if (response.ok) {
            window.location.href = href;
          } else if (response.status === 404) {
            showToast('Page not found. This link may be outdated.', 'error');
          } else {
            window.location.href = href;
          }
        } catch (err) {
          window.location.href = href;
        }
      }
    });
  </script>

  <!-- Analytics tracking -->
  <script src="/static/js/analytics.js"></script>
</body>
</html>