Automating WordPress Publishing with Claude Code
How to build an AI writer and an independent AI reviewer that research a topic, draft a full post in your house style, and upload it to WordPress on a schedule — so every morning you review and publish instead of starting from a blank page.
A Claude Code skill — a reusable instruction file that teaches the assistant your house style and the exact steps to produce a post.
A separate Claude subagent that audits the finished draft with fresh eyes before it is uploaded — the editor to the writer’s author.
A dedicated WordPress user with an Application Password, scoped to the minimum it needs, so a program can create posts on your site.
What This Actually Does
This site’s posts are drafted by an AI assistant on a nightly schedule. Nobody sits down to write them from scratch. A job runs in the small hours, the assistant researches a topic against authoritative sources, writes the post in the site’s house style, generates a cover image, and a second AI reads the whole thing back for accuracy before it is uploaded. By morning there are new drafts waiting in the WordPress dashboard. A human reads each one, fixes anything off, and clicks Publish.
Think of it as a night-shift writing desk. One AI does the blank-page work — the research, the first full draft, the formatting. A second AI plays editor and checks it. The judgement call — whether it is good enough for the world — stays with you. It never publishes on its own. That single rule is what makes the whole thing safe to run unattended, and we will come back to it.
You do not need to be a developer to understand the shape of it, but you will need to be comfortable running a few commands to build it. The rest of this post walks the concept first, then the actual build — the writer, the reviewer, and the plumbing that ties them together — then the mistakes that cost real time so you can skip them.
How the Pieces Fit Together
Five moving parts, each with one job. Two of them are the interesting ones — the writer and the reviewer — and both are just text files you author. The other three are standard infrastructure:
| Part | What it is |
|---|---|
| The writer | A Claude Code skill: an instruction file plus reference material that teaches the assistant how to write a post the way you would. |
| The reviewer | A Claude Code subagent: a second, independent assistant with read-only tools whose only job is to audit the draft. |
| The orchestrator | A plain-text prompt telling the assistant the order of operations — pick a topic, run the skill, call the reviewer, upload as a draft. |
| The door in | A dedicated WordPress user and Application Password, scoped to the minimum, used to create the post over the REST API. |
| The scheduler | Task Scheduler or cron — the operating system’s alarm clock that launches the whole thing at a fixed time. |
Nothing here is exotic. WordPress has exposed a REST API since version 4.7, and Application Passwords for authenticating to it since 5.6. The scheduler already ships with your operating system. The only new ingredients are the two text files that define the writer and the reviewer — and those are the heart of this post.
The Rule That Makes It Safe: Drafts Only
Before any of the build, understand the one non-negotiable. An AI that writes straight to your live site is a bad idea, not because the writing is poor, but because you lose the review step. A wrong command in a technical post, a factual slip, an awkward sentence — all of it is public the instant it is written, with nobody in the loop.
The fix is to make draft the only status the automation is ever allowed to set. The assistant creates the post, WordPress files it as a draft, and it sits invisible to the public until a person publishes it. The morning review is not a nicety you might skip — it is the safety mechanism.
status: draft. It never publishes, never edits a live post, and never deletes anything. A human is always the last step before the public sees a word.
Step 1 — Give WordPress a Dedicated User
Do not hand the automation your own admin login. Create a separate WordPress user for it, give that user the lowest role that can still create posts, and generate an Application Password for that user alone. If the credential ever leaks, the blast radius is one limited account you can delete in seconds — not your administrator.
The Editor role is the right level: it can create, edit, and publish posts and upload media, but cannot touch themes, plugins, settings, or other users. In Users → Add New, create the account (for example blog-bot), set its role to Editor, then open that user’s profile and scroll to Application Passwords. Give the password a name you will recognise, click Add New Application Password, and copy the value it shows you once.
xxxx xxxx xxxx xxxx xxxx xxxx. It is not your login password — it is a revocable token for one application. Store it somewhere safe, never commit it to a repository, and never paste it into a post or a log.
One more requirement from WordPress itself: Application Password authentication only works over HTTPS. If your site is not on TLS yet, fix that first — it is a prerequisite, not an optional hardening step.
Step 2 — Prove the Door Works
Before automating anything, test the credential by hand. The REST API accepts a post at /wp-json/wp/v2/posts, authenticated with the username and Application Password as HTTP Basic auth. This one command creates a draft and proves the whole path works:
# Create a test draft on your site (works on any OS with curl).
# The --user pair is the WordPress username and its Application Password.
curl --user "blog-bot:xxxx xxxx xxxx xxxx xxxx xxxx" \
-H "Content-Type: application/json" \
-d '{"title":"Automation test","status":"draft","content":"<p>Hello from the API.</p>"}' \
https://yourblog.com/wp-json/wp/v2/posts
If it returns a chunk of JSON with an id and "status": "draft", the door works. Log into WordPress and you will see the draft. The fields that matter for a real post are few: status (always draft), title, content (the body as HTML), slug (the URL identifier — set it deliberately for SEO), categories (an array of category IDs), and featured_media (the ID of an uploaded cover image). With the door proven, the rest of the build is about giving the assistant something worth sending through it.
Step 3 — Build the Writer as a Skill
A skill in Claude Code is a folder of instructions the assistant loads on its own when a task matches. Think of it as a job description you write once and the assistant follows every time. It lives under .claude/skills/<name>/ and its entry point is a single Markdown file, SKILL.md, that begins with a short YAML header:
# .claude/skills/my-blog-post/SKILL.md
---
name: my-blog-post
description: Research a topic and write a full draft post in my blog's
house style and HTML components. Use whenever I ask to write, draft,
or generate a blog post.
---
# Blog Post Writer
Read references/writing-style.md first, then follow these steps:
1. Pick the next unused topic from references/topic-backlog.md.
2. Research it on the web. Verify every command and version against
an authoritative source before writing it down.
3. Write the post using the HTML pattern in references/example-post.html.
4. Generate a cover image.
5. Upload to WordPress as a draft. Never publish.
The description line does more than document the skill — it is the trigger. Claude reads it to decide when to load the skill on its own, so write it as the set of situations you want it to fire in, not as a title. A vague description means the skill never activates when you need it; a specific one means it activates exactly when a post is being asked for.
name field in SKILL.md‘s frontmatter must match the containing folder name exactly — .claude/skills/my-blog-post/SKILL.md must declare name: my-blog-post. Get the two out of sync and the skill fails to load with no error to tell you why.
The real leverage is in the references/ folder next to SKILL.md. That is where the house style lives — the files the skill tells the assistant to read before writing:
| Reference file | What it teaches the writer |
|---|---|
writing-style.md |
The voice rules: tone, sentence rhythm, words to avoid, how sections are structured. This is what makes the output sound like you instead of generic AI prose. |
example-post.html |
A real finished post to copy the HTML structure from — headings, callouts, tables, code blocks — so every post uses the same components. |
topic-backlog.md |
The queue of topics to write about. The skill takes the next one each run, so quality tracks how specific your backlog is. |
The full pattern — archetypes, an SEO metadata step, the exact upload mechanics — is more than fits here, but the shape above is the whole idea. If you have never built one, the Claude Code custom skill guide walks through it from an empty folder.
Step 4 — Build the Reviewer as a Subagent
The writer has one unavoidable weakness: it is checking its own work. An assistant that just wrote a post is the worst-placed thing to catch a wrong flag or an invented parameter in it — the same blind spot any author has re-reading their own draft. The fix is a second opinion from something that never saw the writing happen.
That is a subagent: a separate Claude instance, launched for one job, with its own fresh context and its own limited set of tools. It is defined by a Markdown file under .claude/agents/<name>.md, again starting with a YAML header — but this header also declares which tools the agent may use:
# .claude/agents/blog-reviewer.md
---
name: blog-reviewer
description: Independently audit a finished draft post for voice,
technical accuracy, and structure before it is uploaded.
model: haiku
tools: ["Read", "Grep", "Glob", "WebFetch"]
---
You are a senior editor seeing this post for the first time.
You did not write it. Check it against references/writing-style.md,
and re-verify every command in it against an authoritative source.
Return PASS or FAIL, with a list of any blocking issues.
Two choices in that header carry the design. The tools list is deliberately read-only — Read, Grep, Glob, and WebFetch. The reviewer can read the draft and fetch documentation to check facts, but it cannot edit the post. An auditor that can silently rewrite what it is auditing is no longer an independent check. The model line lets you run the reviewer on a cheaper, faster model than the writer, since checking is lighter work than composing — a small cost saving that adds up over nightly runs.
The assistant launches the reviewer through its Agent tool, naming the subagent and handing it just enough to find the files itself — the post’s location, not a summary of what the post says. Summarising the post to its own reviewer would quietly destroy the independence you built it for. Subagents and how they are spawned are covered in depth in the Claude Code subagents guide.
Step 5 — Wire It Together and Run Headlessly
The writer and reviewer are defined but nothing calls them yet. That is the job of one more plain-text file — an orchestration prompt that states the order of operations in prose:
# daily-prompt.md — the single instruction the schedule runs.
Do the following once, then stop:
1. Use the my-blog-post skill to write tonight's post as a draft.
2. When the draft is finished, launch the blog-reviewer subagent
to audit it. If the reviewer returns FAIL, fix the issues and
run it again.
3. Only after a PASS, upload the post to WordPress as a draft.
4. Read the post back and confirm its status is draft.
To run that unattended you need Claude Code in headless mode — one instruction in, a finished result out, no chat window. That is the -p (print) flag: it runs the same agent loop as the interactive tool, prints the result, and exits with a status code a script can check.
# Interactive: opens a chat session you type into.
claude
# Headless: runs one instruction and exits. This is what the schedule calls.
claude -p "Follow the instructions in daily-prompt.md" --output-format text
The catch with unattended runs is permissions. Interactively, Claude Code asks before it runs a command or edits a file. With nobody there to approve, you have to grant those permissions ahead of time in the project’s settings.json, or the run stalls waiting for a click that never comes. Getting that allow-list right is its own topic — the Claude Code permissions and settings.json guide covers it in full.
Step 6 — Put It on a Schedule
The last piece is the alarm clock. On Windows, the Task Scheduler runs a small script at a fixed time. This registers a task that fires at 02:30 every day and catches up if the machine was asleep at that hour:
# Run in an elevated PowerShell prompt. Registers a daily task at 02:30.
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-File C:\blog\run-daily.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At 2:30am
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable
Register-ScheduledTask -TaskName "Blog-Daily-Posts" `
-Action $action -Trigger $trigger -Settings $settings `
-User "yourpc\you" -RunLevel Limited
The -StartWhenAvailable setting is what makes this forgiving: if the PC was off at 02:30, the task runs at the next wake instead of silently skipping the night. The run-daily.ps1 script it points at is short — it loads the Application Password from a secrets file into an environment variable, calls claude -p, and exits. On Linux or macOS the equivalent is a single cron line:
# Linux/macOS: run at 02:30 daily, appending output to a log.
30 2 * * * cd /home/you/blog && claude -p "Follow daily-prompt.md" >> run.log 2>&1
The Gotchas That Cost Real Time
Building this end to end surfaced a handful of failures that look nothing like their cause. These are the ones worth knowing before you hit them yourself. Most come from PowerShell and WordPress each quietly doing something to your text that you did not ask for.
| Symptom | Cause | Fix |
|---|---|---|
Special characters upload as mojibake — a · becomes · |
Windows PowerShell 5.1’s Get-Content reads files as the system codepage, not UTF-8, when no encoding is given. |
Read the HTML with Get-Content -Raw -Encoding UTF8 and send the request body as UTF-8 bytes with charset=utf-8 set explicitly. |
The layout breaks after upload — grid cards collapse, stray <p> tags appear |
WordPress’s wpautop filter rewrites raw HTML, inserting paragraph tags that wreck hand-built markup. |
Wrap the article in a Gutenberg HTML block so WordPress leaves it alone: <!-- wp:html --> … <!-- /wp:html -->. |
| The post saves but the body is empty — title and categories stick, content vanishes | PowerShell 5.1’s ConvertTo-Json escapes a long HTML string into malformed, oversized JSON that WordPress silently drops. |
Build the JSON body by hand with JavaScriptStringEncode instead of ConvertTo-Json for the content field. |
| A post you expected to be a draft is live on the site | A missed or wrong status value, or trusting the API’s response instead of re-checking. |
Always send status: draft, then GET the post back and confirm the status is really draft — do not trust the write response alone. |
| The nightly run fails on a day you used the assistant heavily yourself | The automation shares its usage budget with your interactive sessions when there is no separate API key. | Give the automation its own API key, or keep the nightly workload light so a busy day cannot starve it. |
Adapting It to Your Own Site
Nothing here is specific to one blog. The WordPress side is standard REST and works against any self-hosted WordPress on HTTPS. The scheduler is whatever your operating system already ships — Task Scheduler on Windows, cron on Linux and macOS. The writer and reviewer are just Markdown files you author; the only thing that makes them yours is the reference material you feed the skill.
Start smaller than you think. Get the curl draft in Step 2 working first — that proves the credential and the endpoint. Then build the skill and have it write one post by hand, on demand, before you ever add the reviewer or the schedule. Only once a manual run reliably produces a good draft is it worth automating. The schedule is the last thing you add, not the first.
Final Thoughts
The value here is not that an AI can write — it is that the tedious part of blogging, the blank page, gets handled overnight while the judgement stays with you. The writer researches and drafts, the reviewer checks, and you review, correct, and decide what the world sees. That division of labour is the whole point, and the drafts-only rule is what keeps it honest.
Build it in the order above and it is a weekend project, not a research effort: a dedicated WordPress user, a tested REST call, a writer skill grounded in your house style, an independent reviewer subagent, and a scheduler to fire the lot. The gotchas table is there so the failures that cost hours cost you minutes instead.
Next, we can cover writing the writing-style.md reference itself: how to capture a voice precisely enough that the drafts need less editing each morning.