wordpress-uploader
Original:🇺🇸 English
Translated
WordPress REST API integration for posts and media via deterministic Python scripts. Use when uploading articles, creating drafts, publishing posts, uploading images, editing existing posts, or managing WordPress content. Use for "upload to wordpress", "create wordpress draft", "publish to your-blog", "upload image", or "edit wordpress post". Do NOT use for writing articles (use blog-post-writer) or editing prose style (use anti-ai-editor).
2installs
Added on
NPX Install
npx skill4agent add notque/claude-code-toolkit wordpress-uploaderTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →WordPress Uploader Skill
Operator Context
This skill operates as an operator for WordPress content publishing, configuring Claude's behavior for secure, deterministic REST API operations. It wraps three Python scripts that handle post creation, media uploads, and post editing. LLMs orchestrate. Scripts execute. All WordPress operations go through scripts, never raw API calls.
Hardcoded Behaviors (Always Apply)
- CLAUDE.md Compliance: Read and follow repository CLAUDE.md before any upload
- Draft by Default: Always create posts as drafts unless explicitly told to publish
- Credential Security: Never log, display, or echo the Application Password
- HTTPS Required: Only connect to WordPress sites over HTTPS
- Script Execution Only: All WordPress API calls go through the Python scripts, never via curl or raw requests
- Show Full Output: Display complete script output; never summarize or truncate results
Default Behaviors (ON unless disabled)
- Confirm Before Publish: Ask for user confirmation before setting status to publish
- Title Extraction: Extract title from markdown H1 if is not provided
--title - Human-Readable Mode: Use flag for all script invocations
--human - Post-Upload Verification: Confirm success by checking the returned URL and post ID
Optional Behaviors (OFF unless enabled)
- Direct Publish: Publish immediately instead of draft (requires explicit user request)
- Batch Upload: Upload multiple files in sequence
- Featured Image Workflow: Upload image then attach to post in a single workflow
What This Skill CAN Do
- Create new WordPress posts from markdown files
- Upload images and media to the WordPress media library
- Edit existing posts (title, content, status, featured image, categories, tags)
- Convert markdown to HTML automatically during upload
- Generate Gutenberg blocks for: headings, paragraphs, lists (ordered and unordered), blockquotes, images, separators, fenced code blocks, and button links
- Validate Gutenberg block HTML for structural correctness (flag)
--validate - Set post status: draft, publish, pending, private
- Assign categories and tags by NAME (script looks up IDs via REST API)
- Create tags on the fly if they don't exist in WordPress
- Auto-parse YAML frontmatter for title, categories, tags, slug, excerpt
- Delete old drafts after replacement upload (flag)
--delete - List existing drafts (flag)
--list-drafts - Retrieve existing post details for inspection before editing
What This Skill CANNOT Do
- Work without Application Password authentication configured in
~/.env - Connect to non-HTTPS WordPress sites
- Write or edit article prose (use blog-post-writer or anti-ai-editor)
- Upload to any CMS other than WordPress
Instructions
Phase 1: VALIDATE ENVIRONMENT
Goal: Confirm credentials and target file exist before any API call.
Step 1: Check credentials
Verify contains the required WordPress variables:
~/.envbash
python3 -c "
import os
from pathlib import Path
env = Path(os.path.expanduser('~/.env')).read_text()
required = ['WORDPRESS_SITE', 'WORDPRESS_USER', 'WORDPRESS_APP_PASSWORD']
missing = [v for v in required if v + '=' not in env]
print('OK' if not missing else f'MISSING: {missing}')
"Step 2: Verify source file
If uploading content, confirm the markdown file exists and is non-empty.
Gate: All environment variables present, source file exists. Proceed only when gate passes.
Phase 2: UPLOAD / EXECUTE
Goal: Run the appropriate script for the requested operation.
For new posts:
bash
python3 ~/.claude/scripts/wordpress-upload.py \
--file <path-to-markdown> \
--title "Post Title" \
--humanFor media uploads:
bash
python3 ~/.claude/scripts/wordpress-media-upload.py \
--file <path-to-image> \
--alt "Descriptive alt text" \
--humanFor editing existing posts:
bash
python3 ~/.claude/scripts/wordpress-edit-post.py \
--id <post-id> \
--human \
[--title "New Title"] \
[--content-file updated.md] \
[--featured-image <media-id>] \
[--status draft|publish|pending|private]For inspecting a post before editing:
bash
python3 ~/.claude/scripts/wordpress-edit-post.py \
--id <post-id> \
--get \
--humanGate: Script returns with a valid post/media ID. Proceed only when gate passes.
"status": "success"Phase 3: VERIFY
Goal: Confirm the operation succeeded and report results to the user.
Step 1: Parse script output for post_id, post_url, or media_id
Step 2: Report the result with all relevant URLs (post URL, edit URL, media URL)
Step 3: If this was a publish operation, confirm the post is accessible
Step 4: If part of a multi-step workflow (e.g., image + post + featured image), confirm all steps completed
Gate: User has received confirmation with URLs and IDs. Operation is complete.
Phase 4: POST-UPLOAD (Optional)
Goal: Handle multi-step workflows that combine operations.
Full article with featured image workflow:
bash
# 1. Upload the featured image
python3 ~/.claude/scripts/wordpress-media-upload.py \
--file images/photo.jpg \
--alt "Description" \
--human
# Note the media_id from output
# 2. Create the post (frontmatter auto-parsed for title, categories, tags, slug)
python3 ~/.claude/scripts/wordpress-upload.py \
--file content/article.md \
--category "News" \
--tag "Example Tag" --tag "Example Event" \
--status draft \
--human
# Note the post_id from output
# 3. Attach featured image to post
python3 ~/.claude/scripts/wordpress-edit-post.py \
--id <post_id> \
--featured-image <media_id> \
--humanDraft cleanup workflow (delete old drafts after replacement upload):
bash
# 1. List existing drafts to find old version
python3 ~/.claude/scripts/wordpress-edit-post.py --list-drafts --human
# 2. Delete old draft
python3 ~/.claude/scripts/wordpress-edit-post.py \
--id <old_post_id> \
--delete \
--humanImportant: Always delete old drafts after uploading a replacement. Multiple drafts of the same article accumulate in WordPress and cause confusion.
Script Reference
wordpress-upload.py (Create Posts)
| Flag | Short | Description |
|---|---|---|
| | Path to markdown file (required). Auto-parses YAML frontmatter for title, categories, tags, slug, excerpt. |
| | Post title (extracted from YAML frontmatter or H1 if omitted) |
| | Post status: draft, publish, pending, private |
| Category by NAME, e.g. | |
| Tag by NAME, e.g. | |
| Author user ID | |
| Convert to Gutenberg HTML, validate block structure, print results as JSON, and exit without uploading | |
| Human-readable output |
WordPress categories: Look up your site's category IDs via the REST API or wp-admin. Use category names with and the script resolves IDs automatically.
--categoryYAML frontmatter: The upload script auto-strips frontmatter from the article body. No YAML should appear in the published content. If you see or key-value pairs in the published article, the upload failed to strip it.
---wordpress-media-upload.py (Upload Media)
| Flag | Short | Description |
|---|---|---|
| | Path to media file (required) |
| | Media title (defaults to filename) |
| Alt text for accessibility | |
| Caption for the media | |
| Description for the media | |
| Human-readable output |
wordpress-edit-post.py (Edit Posts)
| Flag | Short | Description |
|---|---|---|
| | Post ID to edit (required, except with |
| Fetch post info without editing | |
| | New post title |
| New content as HTML string | |
| New content from markdown file | |
| | New status: draft, publish, pending, private |
| Featured image media ID. Use to attach uploaded image to post. | |
| Category by NAME (replaces existing) | |
| Tag by NAME (replaces existing) | |
| Post excerpt | |
| Delete the specified post (use to clean up old drafts after replacement upload) | |
| List all draft posts (no | |
| Human-readable output |
Content Formatting
Do NOT include title or author in the article body. WordPress manages these as metadata. Duplicating them in content creates inconsistency when editing in wp-admin.
Supported Gutenberg Block Types
The upload script automatically converts standard markdown to these Gutenberg block types:
| Markdown Syntax | Gutenberg Block | Notes |
|---|---|---|
| | H2-H4 supported; H1 becomes post title |
| Regular text | | Inline bold, italic, links supported |
| | Unordered list |
| | Ordered list with |
| | Blockquote |
| | Standalone images |
| | Horizontal rule |
| | Fenced code block with optional language |
| | Button link |
Code Blocks
Fenced code blocks with optional language hints are converted to blocks:
wp:codemarkdown
```python
def hello():
print("Hello, World!")
```Button Links
Use the attribute to create WordPress button blocks:
{.wp-button}markdown
[Download Now](https://example.com/download){.wp-button}Block Validation
Use to check Gutenberg HTML structure without uploading:
--validatebash
python3 ~/.claude/scripts/wordpress-upload.py --file article.md --validateOutput is JSON: or .
{"status": "valid", "block_count": N}{"status": "invalid", "errors": [...]}For Gutenberg editor compatibility, you can also use raw WordPress block comments between sections:
markdown
Your opening paragraph here.
<!-- wp:separator -->
<hr class="wp-block-separator has-alpha-channel-opacity"/>
<!-- /wp:separator -->
<!-- wp:heading -->
## Section Title
<!-- /wp:heading -->
Section content here.Error Handling
Error: "WORDPRESS_SITE not set" or Missing Credentials
Cause: Environment variables not configured in
Solution:
~/.env- Verify exists in the home directory
~/.env - Check it contains WORDPRESS_SITE, WORDPRESS_USER, and WORDPRESS_APP_PASSWORD
- Ensure no extra whitespace or quoting around values
Error: "401 Unauthorized"
Cause: Invalid or expired Application Password
Solution:
- Log into WordPress admin (wp-admin) > Users > Profile
- Revoke the old Application Password
- Generate a new one and update
~/.env - Verify the username matches the WordPress account exactly
Error: "403 Forbidden"
Cause: WordPress user lacks required capability (e.g., publish_posts, upload_files)
Solution:
- Confirm the user has Editor or Administrator role
- Check if a security plugin is blocking REST API access
- Verify the site allows Application Password authentication
Error: "File not found" or Empty Content
Cause: Incorrect file path or markdown file is empty
Solution:
- Verify the file path with
ls -la <path> - Confirm the file has content (not zero bytes)
- Check for typos in the path, especially the content/ directory structure
Anti-Patterns
Anti-Pattern 1: Publishing Without Confirmation
What it looks like: Setting without asking the user first
Why wrong: Published posts are immediately visible to readers. Mistakes are public.
Do instead: Always create as draft. Ask for explicit confirmation before publishing.
--status publishAnti-Pattern 2: Skipping Environment Validation
What it looks like: Running the upload script without checking credentials first
Why wrong: Produces confusing errors. Wastes time debugging API failures that are just config issues.
Do instead: Complete Phase 1 validation before any script execution.
Anti-Pattern 3: Raw API Calls Instead of Scripts
What it looks like: Using curl or Python requests directly against the WordPress REST API
Why wrong: Bypasses credential handling, error formatting, and markdown conversion built into the scripts.
Do instead: Always use the three provided Python scripts for all WordPress operations.
Anti-Pattern 4: Including Title in Article Body
What it looks like: Markdown file starts with and is also set
Why wrong: Creates duplicate title in WordPress. The H1 renders inside the post body AND as the post title.
Do instead: Either use flag OR include H1 in markdown, never both.
# Article Title--title--titleReferences
This skill uses these shared patterns:
- Anti-Rationalization - Prevents shortcut rationalizations
- Verification Checklist - Pre-completion checks
Domain-Specific Anti-Rationalization
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "Credentials are probably fine" | Config issues cause most upload failures | Run Phase 1 validation |
| "Draft is close enough to publish" | Draft and publish are different states | Confirm desired status explicitly |
| "I'll just curl the API directly" | Scripts handle auth, conversion, errors | Use the provided scripts |
| "Title in body is fine" | Creates duplicate rendering in WordPress | Use --title flag OR H1, not both |
Script Files
- : Create new posts from markdown
scripts/wordpress-upload.py - : Upload images/media to library
scripts/wordpress-media-upload.py - : Edit existing posts (title, content, status, featured image)
scripts/wordpress-edit-post.py