n8n tutorial - Lesson 12: Auto-Publish Blog Posts to Blogger with n8n and AI

Hi everyone, in this session of the n8n workflow automation tutorial series, we build a complete pipeline that uses AI to write a blog post and automatically publishes it as a draft on Blogger. This is a hands-on n8n blogger automation project covering API setup, AI writing, SEO tags, cover image generation, and Drive hosting — all wired together in one workflow.

How to do:

Step 1 — Enable the Blogger API and Set Up OAuth Credentials

Before any n8n node can talk to Blogger, you need to enable the API in Google Cloud and create a credential in n8n.
  1. Go to Google Cloud Console → APIs & Services → Library, search for Blogger API v3, and click Enable. Use your existing project (e.g., Default Gemini Project) — no need to create a new one.
  2. In n8n, go to Credentials → New. Search for Google OAuth2 API — this is the correct type to use because n8n does not have a dedicated Blogger credential. There are three OAuth2 options in n8n:
    • Service-specific credentials (e.g., Google Sheets) — not available for Blogger.
    • Google OAuth2 API ⭐ — pre-configured Auth and Token URLs; only needs Client ID, Client Secret, and Scope.
    • Generic OAuth2 API — requires manual URL entry; more work than needed here.
  3. Fill in the three fields: reuse an existing OAuth Client's Client ID and Client Secret (e.g., from n8n-sheets-client), and set Scope to https://www.googleapis.com/auth/blogger. Name the credential Blogger (Personal).
  4. Create a test workflow called T4-B1-Blogger-Test with two nodes: Manual TriggerHTTP Request. Set the HTTP Request to GET https://www.googleapis.com/blogger/v3/users/self/blogs with authentication set to Predefined Credential Type → Google OAuth2 API → Blogger (Personal).
  5. Execute the workflow. A successful response returns your Blog ID (e.g., 786665955528545962). Save this ID — every subsequent workflow step uses it.

Tip — Reusing one OAuth Client across multiple Google credentials (Sheets, Drive, Blogger, etc.) is a clean production pattern. You can have five or more Google credentials all backed by the same Client ID and Secret without any conflicts.

Step 2 — Build the AI Outline Node

The first AI step generates a structured JSON outline so the second AI call can write a full, fact-accurate article body.
  1. Create the main workflow T4-B2-Blog-Writer. Add a Manual Trigger node, then an Edit Fields node named Set Topic. Add one field: topic with a sample value such as 10 reasons to invest in Vinhomes Ha Long Xanh in 2026.
  2. Add a Basic LLM Chain node named AI Outline. Configure the sub-model as Claude Haiku with Temperature set to 0.3 (low temperature for structured, consistent output).
  3. Write the prompt using four XML blocks:
    • <topic> — inject {{ $json.topic }} from Set Topic.
    • <project_facts> — paste verified project data (e.g., exact figures: 456,639 billion VND, 4,114 ha, 9,056 + 39,000 units). This block prevents hallucination.
    • <task> — instruct the AI to produce a 10-section outline.
    • <output_format> — describe the required JSON shape.
  4. Enable Output Parser and select Generate from JSON Example. Paste a sample JSON with fields: intro, sections (array of 10 objects with title and key_points), conclusion, and meta_keywords (array of 7 strings).
  5. Execute and verify: the output should contain exactly 10 sections and all project figures should match the injected facts precisely.

Production tip — Always inject known facts as a dedicated project_facts block in your prompt rather than relying on the model's training data. This is the primary safeguard against numeric hallucinations in real-estate or any data-heavy content.

Step 3 — Write the Full Article Body with a Second AI Call

The second LLM call receives the structured outline and writes a complete Markdown article — this is the chain-of-prompts pattern.
  1. Add a second Basic LLM Chain node named AI Write Body. Use Claude Haiku with Temperature set to 0.6 (higher than Outline for more natural prose).
  2. In the prompt, inject three things:
    • The full outline JSON: {{ JSON.stringify($('AI Outline').item.json.output) }}.
    • The same project_facts block from Step 2 (paste again for reliability).
    • A style_reference block: paste one existing article as a writing style example so the AI matches your blog's voice.
  3. Do not enable Output Parser here — the desired output is raw Markdown text (~800–1200 words), which is easier to convert to HTML in the next step.
  4. Set Max Tokens in the Anthropic Chat Model sub-node to 8192 (the Claude Haiku 4.5 maximum).
    • Vietnamese text is token-heavy: one accented Vietnamese word ≈ 2–3 tokens.
    • 1,500 Vietnamese words ≈ 4,000–5,000 output tokens — well above the default limit of ~1,024–2,048.
    • Leaving Max Tokens at default silently truncates the article mid-section with no error message.

Note — If you notice a section is missing content in the draft (e.g., the "Legal" section is cut off), the root cause is almost always Max Tokens being too low, not a prompt issue. Always set Max Tokens explicitly for any LLM call expected to produce long-form output.

Step 4 — Add an SEO Meta Tags Node

Insert a third AI call after AI Write Body to generate SEO metadata — this keeps SEO logic separate from content writing.
  1. Add a third Basic LLM Chain node named Claude SEO. Place it between AI Write Body and the Format HTML node. Use Claude Haiku 4.5.
  2. Write a prompt that instructs the model to produce:
    • meta_title: 50–60 characters maximum.
    • meta_description: 130–160 characters with a call to action.
    • tags: array of 5 specific keyword phrases (2–4 words each, topic-specific — avoid generic phrases like "Big Project").
  3. Enable Output Parser → Generate from JSON Example with the shape { "meta_title": "", "meta_description": "", "tags": [] }.
  4. Verify the output quality before moving on:
    • meta_title should be under 60 characters. ✅
    • meta_description should be 130–160 characters and include a CTA. ✅
    • tags should be 5 specific phrases relevant to the article topic. ✅

Tip — After inserting any node in the middle of an existing pipeline, every downstream node that used $input.item.json will break with a TypeError: Cannot read properties of undefined. Always replace $input with $('NodeName') using the exact node name — this is the safest reference pattern in any n8n workflow.

Step 5 — Generate a Cover Image with DALL-E and Host It on Google Drive

This step adds four nodes to create a unique cover image for each post and serve it via a public Drive URL.
  1. Add a Basic LLM Chain node named AI Image Prompt (Claude Haiku). Instruct it to write an English image generation prompt of 40–60 words that visually represents the article topic. Enable Output Parser to get a clean string output.
  2. Add an OpenAI node set to action Generate an Image. Configure:
    • Model: dall-e-3
    • Size: 1024x1024
    • Quality: standard
    • Prompt: reference the output of AI Image Prompt.
    The node returns a temporary DALL-E URL in the url field.
  3. Add an HTTP Request node named Download Image. Set method to GET, URL to {{ $json.url }}, and Response Format to File. This produces binary data that Google Drive can accept.
  4. Add a Google Drive node set to action Upload File. Set the binary input from the previous node and set the destination folder to your Blog Images folder. Note the returned file id.
  5. Add a second Google Drive node set to action Share. Set Role to Reader and Type to Anyone. This step is required separately because Upload does not have an inline "make public" option.
  6. Use this URL format in the Format HTML node to embed the image: https://drive.google.com/thumbnail?id={FILE_ID}&sz=w1024
    • Do not use https://drive.google.com/uc?id=... — Google deprecated this format for hotlinking in 2026. Images will show as broken icons in the published post.
    • The thumbnail endpoint is the current supported method for direct image embedding.

Production tip — During development, use n8n's Pin Data feature on the four image nodes (AI Image Prompt, DALL-E Generate, Download Image, Upload to Drive). Pinned nodes reuse their last output on re-execution instead of running again, saving ~$0.04 per test run. Unpin them only when you are ready for a final production run. Note that Pin Data is a dev-only tool — it cannot be used in batch workflows where each item needs a unique image.

Step 6 — Convert Markdown to HTML and Format the Payload

The Format HTML Code node handles two jobs: converting Markdown to HTML and building the JSON payload for the Blogger API.
  1. Add a Code node named Format HTML after the Make Public Drive node.
  2. Pull the article text from the correct node: const md = $('AI Write Body').item.json.text;. Do not use $input.item.json.text — the direct input parent is now Make Public, not AI Write Body.
  3. Apply regex-based Markdown-to-HTML conversion in sequence:
    • # Heading<h1>
    • ## Heading<h2>
    • ### Heading<h3>
    • **text**<strong>
    • - item<ul><li>
    • Remaining lines → wrapped in <p>
  4. Prepend the cover image at the top of the HTML content: const driveId = $('Upload to Drive').item.json.id; Then build: const imgTag = `<img src="https://drive.google.com/thumbnail?id=${driveId}&sz=w1024" />`; Set html_content = imgTag + convertedHtml;
  5. Pull the SEO tags array: const labels = $('Claude SEO').item.json.output.tags.slice(0, 5); The slice(0, 5) limit is a required workaround — the Blogger API v3 returns HTTP 400 when a specific combination of 7 labels is submitted (root cause unknown after bisect testing; 5 labels always passes).
  6. Build the final output object:
    return [{
      json: {
        title: $('AI Outline').item.json.output.meta_title,
        html_content: html_content,
        labels: labels
      }
    }];

Step 7 — POST the Draft to Blogger via HTTP Request

The final node publishes the formatted post to Blogger as a draft using the API.
  1. Add an HTTP Request node. Set method to POST and URL to: https://www.googleapis.com/blogger/v3/blogs/786665955528545962/posts?isDraft=true Replace the Blog ID with your own value retrieved in Step 1.
  2. Set authentication to Predefined Credential Type → Google OAuth2 API → Blogger (Personal).
  3. Set Body Content Type to JSON. Add three body parameters, each using Expression mode with JSON.stringify():
    • title: {{ JSON.stringify($json.title) }}
    • content: {{ JSON.stringify($json.html_content) }}
    • labels: {{ JSON.stringify($json.labels) }}
  4. Execute the full workflow end-to-end. Verify in your Blogger Dashboard → Posts → Drafts:
    • Post appears as a draft. ✅
    • H1/H2/H3 headings render correctly. ✅
    • Bold text and bullet lists render correctly. ✅
    • Cover image appears at the top of the post. ✅
    • Labels (tags) appear on the draft. ✅
    • No sections are truncated mid-content. ✅

Note — The isDraft=true query parameter keeps every post in draft state so you can review it before publishing. Remove this parameter (or set it to false) only when you are confident the pipeline output quality is consistently good.

Key Lessons from This Session

  1. Use Google OAuth2 API credential type when n8n has no dedicated node for a Google service. It requires only three fields (Client ID, Secret, Scope) and reuses any existing OAuth Client.
  2. Chain-of-prompts (Outline → Body) produces more accurate long-form content than a single prompt. The Outline call locks structure and facts; the Body call focuses entirely on prose quality.
  3. Always inject verified project facts into the prompt as a dedicated block to prevent numeric hallucinations. Never rely on the model's training data for specific figures.
  4. Set Max Tokens explicitly to 8192 for long-form output in Vietnamese (or any token-heavy language). Default limits silently truncate content with no error.
  5. Never use $input references when a node has been inserted in the middle of a pipeline. Always use $('NodeName') to reference a specific upstream node by name.
  6. The Google Drive uc?id= URL format is deprecated for hotlinking (as of 2026). Use https://drive.google.com/thumbnail?id={ID}&sz=w1024 instead.
  7. The Blogger API v3 returns HTTP 400 for certain label combinations beyond 5 entries. Apply .slice(0, 5) as a safe workaround until Google documents the exact limit.
  8. Use n8n's Pin Data feature on expensive nodes (DALL-E, Drive upload) during development. This saves API costs on every re-execution during debugging — unpin only for final production runs.
  9. Verify AI model quota status before building any pipeline around it. Gemini 2.0 Flash free tier was unavailable during this session (error: limit: 0); Claude Haiku 4.5 was the working alternative.

Conclusion:

In this n8n blogger automation tutorial, we built a 9-node pipeline that takes a single topic string, runs it through three AI calls (Outline, Body, SEO), generates a DALL-E cover image, hosts it on Google Drive, and posts a fully formatted draft to Blogger — all automatically. This session is part of the broader n8n tutorial series moving from single-workflow automation toward production-grade, multi-service pipelines. In the next session (T4-B5), we scale this up to a batch workflow that reads 10 topics from a Google Sheet and publishes 10 drafts in one run.

If you have any questions, feel free to leave a comment below. Thank you!

Tags: n8n blogger automation, n8n tutorial, n8n workflow automation, Blogger API OAuth2, AI blog writing n8n, DALL-E Google Drive n8n, LLM chain of prompts, n8n HTTP Request Google API

Maybe you are interested!