
Wordpress Publisher
- 145 installs
- 44 repo stars
- Updated July 8, 2026
- aviz85/claude-skills-library
Publish blog posts, pages, and media to WordPress sites from Claude Code sessions, including drafts, categories, tags, and scheduled releases.
About
The wordpress-publisher skill in aviz85/claude-skills-library automates publishing to WordPress from Claude Code. It streamlines drafting, formatting, categorizing, and pushing posts or pages to live sites, reducing manual CMS work for blogs, marketing sites, and content-heavy SaaS products.
- WordPress REST publishing
- Draft-to-live workflows
- Category and tag management
- Media attachment handling
- Scheduled post support
Wordpress Publisher by the numbers
- 145 all-time installs (skills.sh)
- Ranked #672 of 2,719 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aviz85/claude-skills-library --skill wordpress-publisherAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 145 |
|---|---|
| repo stars | ★ 44 |
| Last updated | July 8, 2026 |
| Repository | aviz85/claude-skills-library ↗ |
What it does
Publish blog posts, pages, and media to WordPress sites from Claude Code sessions, including drafts, categories, tags, and scheduled releases.
Files
WordPress Publisher
First time? Ifsetup_complete: falseabove, run./SETUP.mdfirst, then setsetup_complete: true.
Publish content to WordPress with a two-step flow: draft first, then publish after user confirmation.
Default Language: Hebrew
IMPORTANT: Unless the user explicitly requests English or another language, create all blog posts in Hebrew with RTL formatting. Also generate images using the image-generation skill for:
- Featured/hero image for the post
- Internal images to illustrate concepts (instead of ASCII diagrams)
Always wrap Hebrew content in:
<article dir="rtl" lang="he">
<!-- Hebrew content here -->
</article>Configuration
Create .env file in the skill directory:
# ~/.claude/skills/wordpress-publisher/.env
WP_URL=https://your-site.com
WP_USERNAME=your_username
WP_APP_PASSWORD=YourApplicationPasswordNoSpacesCreating Application Password: 1. Go to WordPress Admin → Users → Profile 2. Scroll to "Application Passwords" 3. Enter a name (e.g., "Claude Code") and click "Add New" 4. Copy the password and remove all spaces
Usage
Create Draft
node ~/.claude/skills/wordpress-publisher/scripts/wp-publish.js create "Post Title" content.htmlCreate with Featured Image
node ~/.claude/skills/wordpress-publisher/scripts/wp-publish.js create "Post Title" content.html --image=cover.jpgCreate and Publish Immediately
node ~/.claude/skills/wordpress-publisher/scripts/wp-publish.js create "Post Title" content.html --publishPublish Existing Draft
node ~/.claude/skills/wordpress-publisher/scripts/wp-publish.js publish POST_IDCheck Post Status
node ~/.claude/skills/wordpress-publisher/scripts/wp-publish.js status POST_IDRead from stdin
echo "<h1>Hello</h1>" | node ~/.claude/skills/wordpress-publisher/scripts/wp-publish.js create "Hello" -Options
| Option | Description |
|---|---|
--publish | Publish immediately (default: draft) |
--image=<path> | Featured image (uploaded to media library) |
--excerpt=<text> | Add excerpt |
--categories=<ids> | Category IDs (comma-separated) |
--tags=<ids> | Tag IDs (comma-separated) |
Response Format
After Creating Draft:
Draft created!
**Post ID:** 123
**Edit in WordPress:** https://your-site.com/wp-admin/post.php?post=123&action=edit
**Preview:** https://your-site.com/?p=123
Publish now or review first?After Publishing:
Post is live!
**URL:** https://your-site.com/your-post-slug/Error Handling
| Error | Cause | Solution |
|---|---|---|
| 401 Unauthorized | Wrong credentials | Check WP_USERNAME and WP_APP_PASSWORD |
| 403 Forbidden | No permissions | Ensure user has Editor/Admin role |
| 404 Not Found | Wrong URL or API disabled | Check WP_URL, enable REST API |
Hebrew/RTL Content
For Hebrew content, wrap in RTL container:
<article dir="rtl" lang="he">
<!-- Hebrew content here -->
</article># WordPress Publisher Configuration
# Copy this file to .env and fill in your credentials
# Your WordPress site URL (no trailing slash)
WP_URL=https://your-site.com
# WordPress username
WP_USERNAME=your_username
# Application Password (remove spaces from the generated password)
# Get one from: WordPress Admin → Users → Profile → Application Passwords
WP_APP_PASSWORD=YourApplicationPasswordNoSpaces
#!/usr/bin/env node
/**
* WordPress Publisher - Create and publish posts to WordPress
*
* Usage:
* node wp-publish.js create <title> <html-file> [--publish]
* node wp-publish.js publish <post-id>
* node wp-publish.js status <post-id>
*
* Environment Variables (from .env in skill directory):
* WP_URL - WordPress site URL
* WP_USERNAME - WordPress username
* WP_APP_PASSWORD - Application password (no spaces)
*
* Options:
* --publish Publish immediately instead of creating draft
* --image=<path> Featured image (uploaded to media library)
* --excerpt=<text> Add excerpt/summary
* --categories=<ids> Comma-separated category IDs
* --tags=<ids> Comma-separated tag IDs
*/
const https = require('https');
const http = require('http');
const fs = require('fs');
const path = require('path');
// Load .env from skill directory
function loadEnv() {
const envPaths = [
path.join(__dirname, '..', '.env'),
path.join(__dirname, '.env')
];
for (const envPath of envPaths) {
if (fs.existsSync(envPath)) {
const content = fs.readFileSync(envPath, 'utf-8');
content.split('\n').forEach(line => {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('#')) {
const eqIndex = trimmed.indexOf('=');
if (eqIndex > 0) {
const key = trimmed.slice(0, eqIndex).trim();
const value = trimmed.slice(eqIndex + 1).trim();
process.env[key] = value;
}
}
});
return true;
}
}
return false;
}
// Make HTTP request to WordPress API
function wpRequest(method, endpoint, data = null) {
return new Promise((resolve, reject) => {
const wpUrl = process.env.WP_URL;
if (!wpUrl) {
reject(new Error('WP_URL not set in environment'));
return;
}
const url = new URL(wpUrl);
const isHttps = url.protocol === 'https:';
const httpModule = isHttps ? https : http;
const auth = Buffer.from(
`${process.env.WP_USERNAME}:${process.env.WP_APP_PASSWORD}`
).toString('base64');
const body = data ? JSON.stringify(data) : null;
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: `/wp-json/wp/v2${endpoint}`,
method: method,
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
'User-Agent': 'curl/8.7.1'
}
};
if (body) {
options.headers['Content-Length'] = Buffer.byteLength(body);
}
const req = httpModule.request(options, (res) => {
let responseBody = '';
res.on('data', chunk => responseBody += chunk);
res.on('end', () => {
try {
const parsed = JSON.parse(responseBody);
if (res.statusCode >= 400) {
reject(new Error(`API Error ${res.statusCode}: ${parsed.message || responseBody}`));
} else {
resolve(parsed);
}
} catch (e) {
reject(new Error(`Invalid JSON response: ${responseBody.slice(0, 200)}`));
}
});
});
req.on('error', reject);
if (body) {
req.write(body);
}
req.end();
});
}
// Upload media (image) to WordPress
function uploadMedia(imagePath) {
return new Promise((resolve, reject) => {
const wpUrl = process.env.WP_URL;
if (!wpUrl) {
reject(new Error('WP_URL not set in environment'));
return;
}
const absolutePath = path.resolve(imagePath);
if (!fs.existsSync(absolutePath)) {
reject(new Error(`Image file not found: ${absolutePath}`));
return;
}
const filename = path.basename(absolutePath);
const ext = path.extname(filename).toLowerCase();
// Determine MIME type
const mimeTypes = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.svg': 'image/svg+xml'
};
const contentType = mimeTypes[ext] || 'application/octet-stream';
const imageData = fs.readFileSync(absolutePath);
const url = new URL(wpUrl);
const isHttps = url.protocol === 'https:';
const httpModule = isHttps ? https : http;
const auth = Buffer.from(
`${process.env.WP_USERNAME}:${process.env.WP_APP_PASSWORD}`
).toString('base64');
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: '/wp-json/wp/v2/media',
method: 'POST',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': contentType,
'Content-Disposition': `attachment; filename="${filename}"`,
'Content-Length': imageData.length,
'User-Agent': 'curl/8.7.1'
}
};
const req = httpModule.request(options, (res) => {
let responseBody = '';
res.on('data', chunk => responseBody += chunk);
res.on('end', () => {
try {
const parsed = JSON.parse(responseBody);
if (res.statusCode >= 400) {
reject(new Error(`Media upload error ${res.statusCode}: ${parsed.message || responseBody}`));
} else {
resolve({
id: parsed.id,
url: parsed.source_url,
title: parsed.title.rendered
});
}
} catch (e) {
reject(new Error(`Invalid JSON response: ${responseBody.slice(0, 200)}`));
}
});
});
req.on('error', reject);
req.write(imageData);
req.end();
});
}
// Create a new post (draft or published)
async function createPost(title, content, options = {}) {
const data = {
title: title,
content: content,
status: options.publish ? 'publish' : 'draft'
};
if (options.excerpt) data.excerpt = options.excerpt;
if (options.categories) data.categories = options.categories;
if (options.tags) data.tags = options.tags;
if (options.featuredMedia) data.featured_media = options.featuredMedia;
const result = await wpRequest('POST', '/posts', data);
return {
id: result.id,
status: result.status,
link: result.link,
editLink: `${process.env.WP_URL}/wp-admin/post.php?post=${result.id}&action=edit`,
previewLink: result.link,
featuredMedia: result.featured_media || null
};
}
// Update post status (publish a draft)
async function publishPost(postId) {
const result = await wpRequest('POST', `/posts/${postId}`, { status: 'publish' });
return {
id: result.id,
status: result.status,
link: result.link
};
}
// Get post status
async function getPostStatus(postId) {
const result = await wpRequest('GET', `/posts/${postId}`);
return {
id: result.id,
title: result.title.rendered,
status: result.status,
link: result.link,
modified: result.modified
};
}
// Parse command line arguments
function parseArgs(args) {
const options = {
command: null,
title: null,
contentFile: null,
postId: null,
publish: false,
image: null,
excerpt: null,
categories: null,
tags: null
};
const positional = [];
for (const arg of args) {
if (arg.startsWith('--')) {
const [key, value] = arg.slice(2).split('=');
switch (key) {
case 'publish':
options.publish = true;
break;
case 'image':
options.image = value;
break;
case 'excerpt':
options.excerpt = value;
break;
case 'categories':
options.categories = value.split(',').map(n => parseInt(n, 10));
break;
case 'tags':
options.tags = value.split(',').map(n => parseInt(n, 10));
break;
}
} else {
positional.push(arg);
}
}
options.command = positional[0];
if (options.command === 'create') {
options.title = positional[1];
options.contentFile = positional[2];
} else if (options.command === 'publish' || options.command === 'status') {
options.postId = positional[1];
}
return options;
}
// Show help
function showHelp() {
console.log(`
WordPress Publisher
===================
Create and publish posts to WordPress via REST API.
Commands:
create <title> <html-file> [options] Create a new post
publish <post-id> Publish a draft post
status <post-id> Get post status
Options for 'create':
--publish Publish immediately (default: draft)
--image=<path> Featured image (uploads to media library)
--excerpt=<text> Add excerpt/summary
--categories=<ids> Comma-separated category IDs
--tags=<ids> Comma-separated tag IDs
Environment Variables (set in .env):
WP_URL WordPress site URL
WP_USERNAME WordPress username
WP_APP_PASSWORD Application password
Examples:
# Create draft from HTML file
node wp-publish.js create "My Post Title" content.html
# Create with featured image
node wp-publish.js create "My Post Title" content.html --image=cover.jpg
# Create and publish immediately with image
node wp-publish.js create "My Post Title" content.html --publish --image=hero.png
# Publish existing draft
node wp-publish.js publish 123
# Check post status
node wp-publish.js status 123
# Read content from stdin
echo "<h1>Hello</h1>" | node wp-publish.js create "Hello Post" -
`);
}
// Read content from file or stdin
async function readContent(filePath) {
if (filePath === '-') {
return new Promise((resolve, reject) => {
let data = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => data += chunk);
process.stdin.on('end', () => resolve(data));
process.stdin.on('error', reject);
});
}
const absolutePath = path.resolve(filePath);
if (!fs.existsSync(absolutePath)) {
throw new Error(`File not found: ${absolutePath}`);
}
return fs.readFileSync(absolutePath, 'utf-8');
}
// Main execution
async function main() {
const args = process.argv.slice(2);
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
showHelp();
process.exit(0);
}
// Load environment
loadEnv();
// Validate environment
if (!process.env.WP_URL || !process.env.WP_USERNAME || !process.env.WP_APP_PASSWORD) {
console.error('Error: Missing required environment variables.');
console.error('Please set WP_URL, WP_USERNAME, and WP_APP_PASSWORD in .env file.');
console.error('Location: ~/.claude/skills/wordpress-publisher/.env');
process.exit(1);
}
const options = parseArgs(args);
try {
switch (options.command) {
case 'create': {
if (!options.title || !options.contentFile) {
console.error('Error: create requires <title> and <html-file>');
process.exit(1);
}
// Upload featured image if provided
let featuredMediaId = null;
if (options.image) {
console.error(`Uploading featured image: ${options.image}`);
const media = await uploadMedia(options.image);
featuredMediaId = media.id;
console.error(`Image uploaded: ${media.url}`);
}
const content = await readContent(options.contentFile);
const result = await createPost(options.title, content, {
publish: options.publish,
excerpt: options.excerpt,
categories: options.categories,
tags: options.tags,
featuredMedia: featuredMediaId
});
console.log(JSON.stringify(result, null, 2));
break;
}
case 'publish': {
if (!options.postId) {
console.error('Error: publish requires <post-id>');
process.exit(1);
}
const result = await publishPost(options.postId);
console.log(JSON.stringify(result, null, 2));
break;
}
case 'status': {
if (!options.postId) {
console.error('Error: status requires <post-id>');
process.exit(1);
}
const result = await getPostStatus(options.postId);
console.log(JSON.stringify(result, null, 2));
break;
}
default:
console.error(`Error: Unknown command '${options.command}'`);
showHelp();
process.exit(1);
}
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
main();
WordPress Publisher - Setup Guide
Prerequisites
- WordPress site with admin access
- REST API enabled (default in modern WP)
1. Create Application Password
1. Log into WordPress Admin 2. Go to Users → Profile 3. Scroll to "Application Passwords" 4. Enter name: "Claude Code" 5. Click "Add New Application Password" 6. Copy the password (remove spaces)
2. Configure Environment
cd ~/.claude/skills/wordpress-publisher
cp .env.example .envEdit .env:
WP_URL=https://your-site.com
WP_USERNAME=your_username
WP_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxxNote: Keep spaces in password or remove them - both work.
3. Test Connection
# Test API access
curl -u "username:app_password" https://your-site.com/wp-json/wp/v2/postsIf you get JSON response, setup is complete!
4. Mark Setup Complete
Edit SKILL.md and change:
setup_complete: trueTroubleshooting
| Issue | Solution |
|---|---|
| 401 Unauthorized | Check username and app password |
| 404 Not Found | Check WP_URL is correct |
| REST API disabled | Install/enable REST API plugin |
| CORS errors | Running from allowed origin |