An open-source, automated WordPress SEO blog content pipeline. It rotates through configurable content silos, researches low-competition questions using SERP data, deduplicates against a local CSV topic ledger, generates an answer-first draft post, publishes to WordPress via REST API, and logs every run for topic tracking and backlink prospecting.
No credentials, no proprietary business logic, no hard-coded site names. Everything is configurable through a single JSON file and standard environment variables.
- Reads your list of content silos (categories) from
config.jsonand determines which category is next in the rotation. - Queries a SERP provider (Apify by default, swappable) for People-Also-Ask questions in that category.
- Filters out questions already in
ledger/topic-ledger.csv. - Picks the lowest-competition uncovered question as the post topic.
- (Optional) Finds a relevant YouTube video to embed.
- Provides a prompt structure for generating an answer-first post with FAQ and optional YMYL (Your Money or Your Life) scaffolding — citations + disclaimer.
- Verifies every internal slug you plan to link resolves to a live WordPress post before the post is submitted.
- Uploads a featured image with alt text and creates the post via the WordPress REST API (as draft or published, your choice).
- Appends a row to
ledger/topic-ledger.csvand any external citation domains toledger/backlink-opportunities.csv.
- Python 3.9+
pip install requests- A WordPress site (self-hosted or WordPress.com Business) with the REST API enabled and an Application Password created under your user profile.
- An Apify account and API token for SERP research. Free tier
is sufficient for low-volume usage. You can replace Apify with any SERP data
source by editing
dispatch_serp_query()inscripts/serp_research.py.
git clone https://github.com/your-org/seo-blog-pipeline.git
cd seo-blog-pipeline
pip install requests
cp config.example.json config.json
# Edit config.json with your settingsEdit config.json. Key fields:
| Field | Description |
|---|---|
site_url |
Base URL of your WordPress site |
categories |
Array of silo objects with name and optional ymyl flag |
rotation_strategy |
"sequential" or "random" |
target_word_count |
Approximate length of generated posts |
publish_status |
"draft", "publish", or "private" |
serp.provider |
"apify" (default) or your custom provider name |
serp.actor_id |
Apify actor ID for Google SERP scraping |
ledger.topics_csv |
Path to topic ledger CSV |
ledger.backlinks_csv |
Path to backlink opportunities CSV |
image.provider |
"dalle", "pexels", "stable-diffusion", or "none" |
See config.example.json for the full annotated structure.
Never put credentials in config.json or on the command line.
Set these before running:
export WP_URL="https://yoursite.com"
export WP_USER="your_wp_username"
export WP_APP_PASSWORD="xxxx xxxx xxxx xxxx xxxx xxxx"
export APIFY_TOKEN="apify_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export YOUTUBE_API_KEY="AIza..." # optional, only if embed_youtube is trueOn Windows (PowerShell):
$env:WP_URL = "https://yoursite.com"
$env:WP_USER = "your_wp_username"
$env:WP_APP_PASSWORD = "xxxx xxxx xxxx xxxx xxxx xxxx"
$env:APIFY_TOKEN = "apify_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"For persistent configuration on Windows use System Properties > Environment
Variables. On Linux/macOS add exports to ~/.profile or use a secrets manager
such as direnv.
python scripts/serp_research.py --category "personal-finance" --config config.jsonAdd --dry-run to preview without writing to the ledger.
python scripts/wp_publish.py check-slugs \
--slugs "best-savings-accounts,emergency-fund-guide,debt-snowball-method"Exits with code 1 and lists any slug that does not resolve to a live post.
python scripts/wp_publish.py publish \
--title "Why an Emergency Fund Matters More Than Investing" \
--content post_body.html \
--category "personal-finance" \
--image featured.jpg \
--image-alt "Glass jar labeled emergency fund sitting on a wooden desk"python scripts/topic_ledger.py --list-topics --config config.json
python scripts/topic_ledger.py --next-category --config config.jsonAdd to your crontab (crontab -e):
0 7 * * 2,4 cd /path/to/seo-blog-pipeline && bash run_pipeline.sh >> logs/pipeline.log 2>&1
Create a Basic Task triggered Weekly (e.g. Tuesday and Thursday, 07:00) with the
action powershell.exe -NonInteractive -File C:\path\to\run_pipeline.ps1.
Set WP_URL, WP_USER, WP_APP_PASSWORD, and APIFY_TOKEN as System
environment variables so the scheduled process can read them.
Both CSV files are auto-created on first use.
ledger/topic-ledger.csv columns:
timestamp, category, topic, post_id, slug, status
ledger/backlink-opportunities.csv columns:
timestamp, domain, anchor_context, post_id, category
This tool publishes content to your WordPress site automatically. You are
responsible for reviewing drafts before they go live (set "publish_status": "draft" during initial setup), ensuring generated content meets your editorial
standards, and complying with the terms of service of any SERP data provider or
API you use. The authors of this repository make no warranties about search ranking
outcomes.
MIT License. See LICENSE file.