Publishing 10 SEO articles a month is manageable. Publishing 40 is a production problem — and the bottleneck is rarely strategy. It's the mechanical work: pulling the next keyword, generating a draft, formatting content, filling WordPress meta fields one at a time, and tracking what went out.
This post walks through a full n8n workflow that automates that entire pipeline from a keyword row in Google Sheets to a fully formatted WordPress draft with SEO fields populated — with a human review gate before anything goes live.
Free workflow JSON at the end.
Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ n8n SEO Workflow │
│ │
│ [Schedule Trigger] OR [Webhook Trigger] │
│ ↓ │
│ [Google Sheets] → read next row where status=pending │
│ ↓ │
│ [Set node] → build AI prompt from keyword + template │
│ ↓ │
│ [AI API Node] → returns structured JSON content │
│ ↓ │
│ [Function Node] → parse + map fields │
│ ↓ ↓ │
│ [WordPress: Upload Image] [WordPress: Create Draft] │
│ ↓ │
│ [Google Sheets: Update status → published] │
│ ↓ │
│ [Slack / Email: Notify reviewer] │
│ ↓ │
│ [Error Branch] │
│ Log failure → Alert team → Set status → failed │
└─────────────────────────────────────────────────────────┘
Stack
| Tool | Role | Required? |
|---|---|---|
| n8n | Workflow orchestration | Yes |
| Google Sheets | Keyword input + status tracking | Yes |
| OpenAI / Claude / Gemini | AI content generation | Yes |
| WordPress REST API | Draft creation + meta population | Yes |
| Rank Math or Yoast (REST ext.) | SEO meta fields | Recommended |
| Slack or Email | Review notification | Recommended |
Node-by-Node Breakdown
1. Trigger Configuration
Two supported trigger modes:
Schedule Trigger — runs on a cron cadence (daily, 3x/week, etc). Best for steady publishing velocity.
Cron: 0 8 * * 1,3,5 # Mon/Wed/Fri at 8am
Webhook Trigger — on-demand generation triggered from a form, Slack command, or external system. Best for trending keyword response or client-driven requests.
Both trigger types feed into the same Google Sheets read node.
2. Google Sheets — Keyword Input
Recommended Column Structure
| Column | Purpose |
|---|---|
keyword |
Target keyword for this article |
slug |
URL slug (pre-defined or auto-generated) |
content_type |
blog_post, faq_page, location_page
|
word_count |
Target word count for AI prompt |
category |
WordPress category ID |
status |
pending, in_progress, published, failed
|
draft_url |
Populated after draft creation |
generated_at |
Timestamp of last run |
Read Logic
Configure the Google Sheets node to filter for status = pending and return the first matching row only. Process one keyword per run to stay within API rate limits and keep runs predictable.
After reading the row, immediately update status → in_progress with a separate Sheets write node. This prevents duplicate processing if the workflow is triggered again before the first run completes.
// Set node — build processing context
{
keyword: "{{ $json.keyword }}",
slug: "{{ $json.slug }}",
content_type: "{{ $json.content_type }}",
word_count: "{{ $json.word_count || 1200 }}",
category: "{{ $json.category }}"
}
3. AI Content Generation
Prompt Structure
The quality of generated content is determined almost entirely by prompt quality. A vague prompt produces generic output regardless of which model you use.
You are an SEO content writer producing articles for [Site Name].
Target audience: [description]
Writing style: [conversational / authoritative / technical]
Tone: [direct, avoid filler phrases, no "In conclusion" openers]
Produce a complete SEO article for the following keyword:
Keyword: {{ $json.keyword }}
Content type: {{ $json.content_type }}
Target word count: {{ $json.word_count }}
Return ONLY valid JSON with this exact structure:
{
"meta_title": "...", // Under 60 chars, include keyword
"meta_description": "...", // Under 155 chars, include keyword, action-oriented
"h1": "...", // Main headline, different from meta_title
"outline": ["H2 1", "H2 2", "H2 3", "H2 4"],
"body_html": "...", // Full article in HTML, using <h2>, <p>, <ul> tags
"focus_keyword": "...", // Exact match target keyword
"faq": [
{ "question": "...", "answer": "..." },
{ "question": "...", "answer": "..." }
]
}
No preamble. No markdown fences. Valid JSON only.
Routing by Content Type
Use an n8n Switch node to route different content types to different prompt templates before the AI call:
content_type == "blog_post" → Blog post prompt template
content_type == "faq_page" → FAQ-focused prompt template
content_type == "location_page" → Location page prompt template
Parsing the Response
// Function node — parse AI JSON response
const raw = $input.first().json.content[0].text;
// Strip any accidental markdown fences
const clean = raw.replace(/```
{% endraw %}
json|
{% raw %}
```/g, '').trim();
let parsed;
try {
parsed = JSON.parse(clean);
} catch (e) {
throw new Error('AI response was not valid JSON: ' + raw.substring(0, 200));
}
return [{
json: {
meta_title: parsed.meta_title,
meta_description: parsed.meta_description,
h1: parsed.h1,
body_html: parsed.body_html,
focus_keyword: parsed.focus_keyword,
faq_schema: JSON.stringify(parsed.faq),
outline: parsed.outline
}
}];
4. WordPress Draft Creation
Authentication
Use WordPress Application Passwords — available natively since WordPress 5.6. Create a dedicated application password for the n8n integration with the minimum role needed (Editor is sufficient for draft creation).
Store the base64-encoded username:app_password string in n8n credentials, not hardcoded in nodes.
// Authorization header value
'Basic ' + btoa('your_username:xxxx xxxx xxxx xxxx xxxx xxxx')
Create Draft Post
// WordPress REST API — POST /wp-json/wp/v2/posts
{
title: "{{ $json.h1 }}",
content: "{{ $json.body_html }}",
status: "draft", // ALWAYS draft, never publish directly
slug: "{{ $json.slug }}",
categories: ["{{ $json.category }}"],
meta: {
// Rank Math fields
"rank_math_focus_keyword": "{{ $json.focus_keyword }}",
"rank_math_description": "{{ $json.meta_description }}",
"rank_math_title": "{{ $json.meta_title }}"
}
}
Important: Always create as status: draft. Never set status: publish in the automation. The human review gate between draft creation and publish is what makes this workflow safe to run unattended.
Featured Image Upload
Upload the image to the WordPress media library before creating the post, then attach the returned media ID:
// POST /wp-json/wp/v2/media
// Headers: Content-Disposition: attachment; filename="post-image.jpg"
// Body: raw image binary
// Response includes: { id: 1234, ... }
// Use this ID in post creation: featured_media: mediaId
Yoast SEO Fields (Alternative)
If the site runs Yoast instead of Rank Math:
meta: {
"_yoast_wpseo_title": "{{ $json.meta_title }}",
"_yoast_wpseo_metadesc": "{{ $json.meta_description }}",
"_yoast_wpseo_focuskw": "{{ $json.focus_keyword }}"
}
Note: Yoast meta fields via REST require the yoast/v1 endpoint or a plugin that exposes custom meta through the standard REST API. Test this before building the full workflow.
5. Status Update + Review Notification
After successful draft creation:
// Update Google Sheets row
status → "published"
draft_url → "https://yoursite.com/wp-admin/post.php?post=POST_ID&action=edit"
generated_at → new Date().toISOString()
Then send a Slack notification:
{
text: "📝 New SEO draft ready for review",
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*Keyword:* ${keyword}\n*Title:* ${h1}\n*Word count:* ~${wordCount}`
}
},
{
type: "actions",
elements: [{
type: "button",
text: { type: "plain_text", text: "Review Draft in WordPress" },
url: draftUrl
}]
}
]
}
6. Error Handling
Every API call needs an error branch. Configure n8n's Error Trigger or use per-node error outputs.
// Error branch function node
const errorDetails = {
timestamp: new Date().toISOString(),
workflow: "SEO Content Pipeline",
failed_node: $input.first().json.node || "unknown",
error_message: $input.first().json.error?.message || "Unknown error",
keyword: $('Google Sheets').first().json.keyword || "unknown",
status_code: $input.first().json.error?.statusCode
};
return [{ json: errorDetails }];
After logging to Sheets, send an alert to Slack and update the keyword row to status: failed.
Common Failure Points
AI API timeout on long content: Configure a 60s timeout, retry once before alerting. Long word count targets (2000+ words) increase timeout risk.
WordPress 401: Application password expired or revoked. Alert the team — this requires manual credential refresh. Do not retry.
WordPress 422 (Unprocessable Entity): Usually a malformed meta field or category ID that doesn't exist. Log the full request body alongside the error for debugging.
Google Sheets quota exceeded: Polling too frequently. Back off the trigger cadence or batch keyword reads.
JSON parse failure on AI response: The model occasionally wraps output in markdown fences despite instructions not to. The strip-and-parse pattern in step 3 catches most of these, but log the raw response for any that still fail.
What This Pipeline Can't Do
Verify factual accuracy. The AI model generates plausible content. For technical, medical, legal, or financial topics, plausible and accurate are not the same thing. Human review is mandatory, not optional.
Research the SERP. Without a SERP research step (DataForSEO, Google Search API), the AI generates against the keyword text alone — it doesn't know what's ranking, what intent signals exist, or what competing content looks like. Adding a research step before the AI call significantly improves output relevance.
Populate internal links. The model has no visibility into your site's existing content. Internal linking requires a separate step or manual addition during review.
Replace editorial judgment. The workflow handles production throughput. Which keywords to target, what angle to take, whether the output genuinely helps the reader — those remain human decisions.
Customisation Options
Swap the Keyword Source
Google Sheets is the default, but Airtable, Notion databases, and database webhooks all work as input sources with node reconfiguration. The key requirement is a status field to prevent duplicate processing.
Add SERP Research
Before the AI call, add a DataForSEO node or Google Search API call:
[Google Sheets: Read keyword]
↓
[DataForSEO: Get top 10 results for keyword]
↓
[Function: Extract titles, meta descriptions, H2s from results]
↓
[AI Node: Generate content informed by SERP data]
This adds latency but significantly improves content relevance and competitive positioning.
Multi-Site or Multi-Client
Run separate workflow instances per client/site rather than conditional branching inside one workflow. Separation is easier to debug, easier to permission, and easier to hand off to a client if needed.
Approval Gate
For regulated industries or quality-sensitive publishing:
[Draft created in WordPress]
↓
[Send approval email with Approve/Reject links]
↓
[n8n Wait node — pauses workflow]
↓
[Webhook receives approval decision]
↓
[IF approved → update status → notify]
[IF rejected → flag for manual edit → notify]
Rate Limits at Scale
| Service | Limit to Watch |
|---|---|
| OpenAI (GPT-4o) | 10,000 TPM on Tier 1, scales with usage |
| Claude API | 40 RPM on base tier |
| Google Sheets | 300 reads/minute per project |
| WordPress REST API | No hard limit, but server resources apply |
For batches over 20 posts per run, add a delay node between keyword iterations (30–60 seconds). For very high volume, consider running multiple workflow instances on staggered schedules rather than one large batch.
Get the Free Workflow JSON
IT Path Solutions has published the complete n8n workflow — Google Sheets trigger, AI generation node with structured prompt, WordPress REST API publish node, Slack notification, status update logic, and error handling branch — all pre-connected.
Import into any n8n instance (self-hosted or Cloud), add credentials, run against one keyword first.
👉 Download the free n8n SEO workflow JSON + setup guide
Setup guide covers: Google Sheets OAuth, AI API key configuration, WordPress Application Password setup, Slack webhook, prompt customisation by content type.
When You Need a Custom Build
The template handles one site, one content type, standard WordPress. Common customisation needs:
- Keyword lists with thousands of entries requiring batching, deduplication, and queue management
- Custom post types or non-standard WordPress metadata beyond the default REST API
- Multi-stage approval workflows for regulated industries
- Integration with existing CRM, analytics platform, or project management tooling
- Multi-site or multi-client deployments with separate keyword sources and review channels
IT Path Solutions builds custom n8n pipelines for content teams and agencies at scale.
Summary
The pipeline works because it eliminates the mechanical steps — keyword assignment, draft generation, meta field population, WordPress upload, reviewer notification — while keeping a human in the loop before anything goes live. The result is publishing throughput that scales with keyword volume, not headcount.
Full setup guide and workflow JSON: itpathsolutions.com/n8n-seo-automation-workflow
Running a version of this in production? What broke first? Drop it in the comments.
Top comments (0)