Install
openclaw skills install wordpress-bloggerPublish articles to WordPress blogs via REST API. Handles post creation, category/tag management, and SEO-friendly English slug generation. Use when user ask...
openclaw skills install wordpress-bloggerPublish articles to WordPress blogs safely with automatic category/tag management and English URL slugs.
WordPress credentials must be configured in the workspace .env file:
# WordPress Blog Credentials
WP_BLOG_URL="https://blog.example.com" # Blog base URL (no trailing slash)
WP_USERNAME="your_username" # WordPress admin username
WP_APP_PASSWORD="xxxx xxxx xxxx xxxx xxxx" # Application password
How to create an Application Password:
Read credentials from workspace .env:
# Load credentials from .env file
source /root/.openclaw/workspace/.env
WP_URL="${WP_BLOG_URL:-https://blog.example.com}"
WP_USER="${WP_USERNAME:-admin}"
WP_PASS="${WP_APP_PASSWORD}"
# Verify credentials exist
if [ -z "$WP_PASS" ]; then
echo "❌ Error: WP_APP_PASSWORD not found in .env file"
exit 1
fi
Before publishing, analyze the article content to generate appropriate metadata:
Create a URL-friendly English slug from the article title or content:
Examples:
ryzen-7950x-vs-i9-13900k-benchmark-comparisonoptimize-database-performance-productionunderstanding-container-orchestration-kubernetesBased on article content, suggest appropriate WordPress categories and tags:
| Content Type | Suggested Categories | Suggested Tags |
|---|---|---|
| Hardware reviews | Hardware, Reviews | CPU, benchmark, performance, AMD, Intel |
| Software development | Development, Programming | coding, best-practices, architecture |
| AI/LLM related | AI, Technology | machine-learning, LLM, artificial-intelligence |
| Career development | Career | career-growth, soft-skills, productivity |
| DevOps/Infrastructure | DevOps, Infrastructure | docker, kubernetes, ci-cd, cloud |
If user doesn't specify, use these reasonable defaults:
Check if category exists, create if not:
CATEGORY_NAME="Hardware" # Use suggested or user-specified category
# Try to find existing category
CAT_ID=$(curl -s "${WP_URL}/wp-json/wp/v2/categories?search=${CATEGORY_NAME}&per_page=1" \
-u "${WP_USER}:${WP_PASS}" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
# Create if not exists
if [ -z "$CAT_ID" ]; then
CAT_RESULT=$(curl -s -X POST "${WP_URL}/wp-json/wp/v2/categories" \
-u "${WP_USER}:${WP_PASS}" \
-H "Content-Type: application/json" \
-d "{\"name\": \"${CATEGORY_NAME}\"}")
CAT_ID=$(echo "$CAT_RESULT" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
fi
echo "Category ID: $CAT_ID"
For each tag, check existence and create if needed:
TAGS=("CPU" "Benchmark" "AMD" "Performance") # Use suggested or user-specified tags
TAG_IDS=""
for TAG in "${TAGS[@]}"; do
# Try to find existing tag
TID=$(curl -s "${WP_URL}/wp-json/wp/v2/tags?search=${TAG}&per_page=1" \
-u "${WP_USER}:${WP_PASS}" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
# Create if not exists
if [ -z "$TID" ]; then
TAG_RESULT=$(curl -s -X POST "${WP_URL}/wp-json/wp/v2/tags" \
-u "${WP_USER}:${WP_PASS}" \
-H "Content-Type: application/json" \
-d "{\"name\": \"${TAG}\"}")
TID=$(echo "$TAG_RESULT" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
fi
TAG_IDS="${TAG_IDS},${TID}"
done
# Remove leading comma
TAG_IDS=$(echo "$TAG_IDS" | sed 's/^,//')
echo "Tag IDs: $TAG_IDS"
TITLE="AMD Ryzen 9 7950X vs Intel Core i9-13900K: A Detailed Benchmark Comparison"
CONTENT="<p>In this comprehensive benchmark analysis...</p>" # Convert markdown to HTML
SLUG="ryzen-7950x-vs-i9-13900k-benchmark-comparison"
EXCERPT="We compare two flagship processors across gaming, productivity, and power efficiency."
# Create post
POST_RESULT=$(curl -s -X POST "${WP_URL}/wp-json/wp/v2/posts" \
-u "${WP_USER}:${WP_PASS}" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"${TITLE}\",
\"content\": \"${CONTENT}\",
\"slug\": \"${SLUG}\",
\"status\": \"publish\",
\"categories\": [${CAT_ID}],
\"tags\": [${TAG_IDS}],
\"excerpt\": \"${EXCERPT}\"
}")
POST_ID=$(echo "$POST_RESULT" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
echo "Created post ID: $POST_ID"
If updating an existing post (e.g., adding categories/tags to a draft):
POST_ID="123" # Existing post ID
UPDATE_RESULT=$(curl -s -X POST "${WP_URL}/wp-json/wp/v2/posts/${POST_ID}" \
-u "${WP_USER}:${WP_PASS}" \
-H "Content-Type: application/json" \
-d "{
\"categories\": [${CAT_ID}],
\"tags\": [${TAG_IDS}],
\"slug\": \"${SLUG}\"
}")
Construct the public viewing URL (not the API endpoint):
# WordPress permalink structure: /{slug}/
PUBLIC_URL="${WP_URL}/${SLUG}/"
# If slug not set, use post ID format
if [ -z "$SLUG" ]; then
PUBLIC_URL="${WP_URL}/?p=${POST_ID}"
fi
echo "✅ Article published successfully!"
echo ""
echo "📄 Title: ${TITLE}"
echo "🔗 URL: ${PUBLIC_URL}"
echo "📁 Category: ${CATEGORY_NAME}"
echo "🏷️ Tags: ${TAGS[*]}"
WordPress content field requires HTML. Convert markdown:
| Markdown | HTML |
|---|---|
# Title | <h1>Title</h1> |
## Subtitle | <h2>Subtitle</h2> |
### H3 | <h3>H3</h3> |
**bold** | <strong>bold</strong> |
*italic* | <em>italic</em> |
- list item | <ul><li>list item</li></ul> |
1. item | <ol><li>item</li></ol> |
[text](url) | <a href="url">text</a> |
`code` | <code>code</code> |
code block | <pre><code>code block</code></pre> |
Escape double quotes in content when building JSON:
# Escape quotes for JSON
ESCAPED_CONTENT=$(echo "$CONTENT" | sed 's/"/\\"/g')
#!/bin/bash
# Load credentials
source /root/.openclaw/workspace/.env
WP_URL="${WP_BLOG_URL:-https://blog.example.com}"
WP_USER="${WP_USERNAME:-admin}"
WP_PASS="${WP_APP_PASSWORD}"
# Article content - CPU Benchmark example
TITLE="AMD Ryzen 9 7950X vs Intel Core i9-13900K: A Detailed Benchmark Comparison"
SLUG="ryzen-7950x-vs-i9-13900k-benchmark-comparison"
CATEGORY="Hardware"
TAGS=("CPU" "Benchmark" "AMD" "Intel" "Performance")
CONTENT="<p>The battle for desktop CPU supremacy continues...</p><h2>Test Methodology</h2><p>All tests were conducted on identical platforms...</p>"
# Step 1: Create/Get Category
CAT_RESULT=$(curl -s -X POST "${WP_URL}/wp-json/wp/v2/categories" \
-u "${WP_USER}:${WP_PASS}" \
-H "Content-Type: application/json" \
-d "{\"name\": \"${CATEGORY}\"}")
CAT_ID=$(echo "$CAT_RESULT" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
# Step 2: Create/Get Tags
TAG_IDS=""
for TAG in "${TAGS[@]}"; do
TAG_RESULT=$(curl -s -X POST "${WP_URL}/wp-json/wp/v2/tags" \
-u "${WP_USER}:${WP_PASS}" \
-H "Content-Type: application/json" \
-d "{\"name\": \"${TAG}\"}")
TID=$(echo "$TAG_RESULT" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
TAG_IDS="${TAG_IDS},${TID}"
done
TAG_IDS=$(echo "$TAG_IDS" | sed 's/^,//')
# Step 3: Create Post
POST_RESULT=$(curl -s -X POST "${WP_URL}/wp-json/wp/v2/posts" \
-u "${WP_USER}:${WP_PASS}" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"${TITLE}\",
\"content\": \"${CONTENT}\",
\"slug\": \"${SLUG}\",
\"status\": \"publish\",
\"categories\": [${CAT_ID}],
\"tags\": [${TAG_IDS}]
}")
POST_ID=$(echo "$POST_RESULT" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)
PUBLIC_URL="${WP_URL}/${SLUG}/"
echo "✅ Published: ${PUBLIC_URL}"
| Error | Cause | Solution |
|---|---|---|
401 Unauthorized | Invalid credentials | Check username and app password |
403 Forbidden | Insufficient permissions | Use admin account or check user capabilities |
rest_cannot_create | Missing edit_posts capability | Verify user has publishing permissions |
term_exists | Category/tag already exists | Fetch existing ID instead of creating |
Always check API responses for errors:
if echo "$RESULT" | grep -q '"code":"'; then
ERROR_CODE=$(echo "$RESULT" | grep -o '"code":"[^"]*"' | head -1)
ERROR_MSG=$(echo "$RESULT" | grep -o '"message":"[^"]*"' | head -1)
echo "❌ API Error: $ERROR_CODE - $ERROR_MSG"
exit 1
fi
After successful publication, respond with:
✅ Article published successfully!
📄 Title: [Article Title]
🔗 URL: [Public Viewing URL]
📁 Category: [Category Name]
🏷️ Tags: [Tag List]