# How the /publish-draft Command Integrates with WordPress REST API and Yoast SEO

> Learn how the /publish-draft command automates WordPress publishing using the REST API and Yoast SEO integration. Effortlessly manage your content workflow.

- Repository: [Craig/seomachine](https://github.com/TheCraigHewitt/seomachine)
- Tags: deep-dive
- Published: 2026-03-12

---

**The `/publish-draft` command automates WordPress publishing by parsing markdown drafts, creating taxonomies via REST endpoints, and injecting Yoast SEO meta fields through a custom MU-plugin that exposes `yoast_seo` as a writable REST field.**

The `publish-draft` command in the TheCraigHewitt/seomachine repository streamlines content workflows by bridging Claude's AI capabilities with WordPress's REST API and Yoast SEO optimization. This command orchestrates a complete publishing pipeline that converts markdown drafts into fully optimized WordPress posts with proper taxonomies and SEO metadata.

## How the /publish-draft Command Works

When invoked, the `/publish-draft` command triggers the **`WordPressPublisher`** class located in [`data_sources/modules/wordpress_publisher.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/data_sources/modules/wordpress_publisher.py). This class implements a six-step workflow that handles everything from markdown parsing to Yoast meta injection.

The command accepts a file path and optional post type parameter, then executes a chain of methods that validate credentials, parse content, ensure taxonomies exist, create the draft, and finally optimize it for search engines.

## Step-by-Step WordPress REST API Integration

### Parsing and Converting Markdown Content

The process begins with **`parse_draft_file()`**, which reads the markdown file and extracts critical metadata. This method identifies the H1 title from the markdown body and parses front-matter fields including `Meta Title`, `Meta Description`, `Target Keyword`, `Category`, `Tags`, and `URL Slug`【5†L53-L87】.

Once parsed, the **`markdown_to_html()`** method converts the remaining markdown body to HTML using the `markdown` library, producing WordPress-ready content【5†L37-L51】.

### Creating Categories and Tags via REST

Before publishing, the system ensures required taxonomies exist. The **`get_or_create_category()`** and **`get_or_create_tag()`** methods interact with the WordPress REST API at `/wp-json/wp/v2/categories` and `/wp-json/wp/v2/tags` respectively【5†L83-L106】【5†L115-L124】.

These methods first query existing terms by name. If a term is missing, they send a POST request to create it, returning the term ID for association with the new post.

### Publishing the Draft Post

The **`create_draft()`** method constructs a JSON payload containing the parsed title, HTML content, URL slug, status set to `draft`, and optional category and tag IDs. It POSTs this data to `/wp-json/wp/v2/{post_type}`, where `post_type` defaults to `posts` but can be `pages` or custom types【5†L65-L88】【5†L90-L108】.

Upon successful creation, WordPress returns the post ID, which the system uses for subsequent Yoast meta updates.

## Yoast SEO Integration via MU-Plugin

### How the Yoast MU-Plugin Exposes REST Fields

The integration relies on **[`wordpress/seo-machine-yoast-rest.php`](https://github.com/TheCraigHewitt/seomachine/blob/main/wordpress/seo-machine-yoast-rest.php)**, a Must-Use plugin that runs on every WordPress request. This plugin registers Yoast SEO meta fields with `show_in_rest => true`, making them accessible via the REST API【6†L52-L62】.

Specifically, it creates a composite REST field named **`yoast_seo`** that bundles three critical SEO fields: `focus_keyphrase`, `seo_title`, and `meta_description`【6†L75-L84】. This abstraction simplifies the API contract, allowing the Python publisher to send a single structured object rather than individual meta fields.

### Setting Yoast Meta Data via the REST API

After creating the draft post, **`set_yoast_meta()`** sends a second POST request to the same endpoint (`/wp-json/wp/v2/{post_type}/{id}`) with a payload containing the `yoast_seo` object. The JSON structure includes the SEO title, meta description, and target keyword extracted from the draft's front matter【5†L12-L18】【5†L33-L42】.

The MU-plugin's `update_callback` processes this payload, validating user permissions and updating the corresponding post meta entries (`_yoast_wpseo_focuskw`, `_yoast_wpseo_title`, `_yoast_wpseo_metadesc`). This makes the SEO data instantly available in the WordPress admin and visible to Yoast's analysis tools.

## Code Examples

### Using the /publish-draft Command

Invoke the command from Claude's interface with the draft file path:

```text
/publish-draft drafts/content-marketing-guide-2025-12-10.md

```

This creates a standard blog post with default SEO metadata.

To create a WordPress page instead:

```text
/publish-draft drafts/pricing-comparison.md --type page

```

For custom post types:

```text
/publish-draft drafts/product-comparison.md --type compare

```

*Note: The custom post type must be registered in WordPress before publishing.*

### Direct Python Implementation

For automation workflows, import the `WordPressPublisher` class directly:

```python
from data_sources.modules.wordpress_publisher import WordPressPublisher

# Initialize with environment variables (WORDPRESS_URL, WORDPRESS_USERNAME, WORDPRESS_APP_PASSWORD)

publisher = WordPressPublisher()

# Publish the draft

result = publisher.publish_draft(
    file_path="drafts/content-marketing-guide-2025-12-10.md",
    post_type="post"  # Options: "post", "page", or custom types

)

# Access the WordPress edit URL

print("Review draft at:", result["edit_url"])

```

This approach allows integration with CI/CD pipelines or content calendars.

### REST API Request Structure for Yoast

The underlying HTTP request for updating Yoast SEO fields follows this pattern:

```http
POST https://example.com/wp-json/wp/v2/posts/123
Authorization: Basic base64(username:app_password)
Content-Type: application/json

{
  "yoast_seo": {
    "seo_title": "Best Content Marketing Guide 2025",
    "meta_description": "A complete guide on content marketing tactics for 2025.",
    "focus_keyphrase": "content marketing guide"
  }
}

```

The MU-plugin receives this payload, validates the user, and maps the fields to the underlying Yoast meta keys.

## Key Files and Implementation Details

| File | Role | Key Components |
|------|------|----------------|
| [`.claude/commands/publish-draft.md`](https://github.com/TheCraigHewitt/seomachine/blob/main/.claude/commands/publish-draft.md) | Command definition and usage documentation | CLI syntax, parameter descriptions, environment variable requirements |
| [`data_sources/modules/wordpress_publisher.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/data_sources/modules/wordpress_publisher.py) | Core Python implementation | `WordPressPublisher` class, `parse_draft_file()`, `create_draft()`, `set_yoast_meta()` |
| [`wordpress/seo-machine-yoast-rest.php`](https://github.com/TheCraigHewitt/seomachine/blob/main/wordpress/seo-machine-yoast-rest.php) | WordPress MU-plugin | REST field registration, `yoast_seo` composite field, meta update callbacks |

The integration requires the MU-plugin to be installed in the WordPress `wp-content/mu-plugins/` directory, while the Python publisher relies on environment variables (`WORDPRESS_URL`, `WORDPRESS_USERNAME`, `WORDPRESS_APP_PASSWORD`) for authentication.

## Summary

- The **`/publish-draft`** command automates WordPress publishing through the `WordPressPublisher` class in [`data_sources/modules/wordpress_publisher.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/data_sources/modules/wordpress_publisher.py).
- It parses markdown drafts, extracts front-matter SEO data, and converts content to HTML for WordPress ingestion.
- Taxonomy creation (categories and tags) occurs dynamically via the WordPress REST API endpoints `/wp-json/wp/v2/categories` and `/wp-json/wp/v2/tags`.
- The **[`wordpress/seo-machine-yoast-rest.php`](https://github.com/TheCraigHewitt/seomachine/blob/main/wordpress/seo-machine-yoast-rest.php)** MU-plugin exposes Yoast SEO fields via a composite `yoast_seo` REST field, enabling the publisher to set meta titles, descriptions, and focus keywords through standard REST API POST requests.
- Authentication relies on WordPress Application Passwords and Basic Auth headers for secure API communication.

## Frequently Asked Questions

### How does the /publish-draft command authenticate with WordPress?

The command uses WordPress Application Passwords for secure REST API authentication. The `WordPressPublisher` class reads credentials from environment variables (`WORDPRESS_URL`, `WORDPRESS_USERNAME`, `WORDPRESS_APP_PASSWORD`) and transmits them via HTTP Basic Auth headers with every API request. This approach follows WordPress security best practices while enabling automated publishing workflows without storing plaintext passwords.

### What Yoast SEO fields can be set through the REST API integration?

The integration supports setting three primary Yoast SEO fields through the composite `yoast_seo` REST object: `seo_title` (the optimized browser title), `meta_description` (the search engine snippet), and `focus_keyphrase` (the target keyword for content analysis). These fields map to Yoast's internal meta keys (`_yoast_wpseo_title`, `_yoast_wpseo_metadesc`, `_yoast_wpseo_focuskw`) via the MU-plugin's update callbacks, making the data immediately available in the WordPress admin interface.

### Can I publish to custom post types using the /publish-draft command?

Yes, the command supports custom post types through the `--type` parameter. When invoking the command, you can specify any registered WordPress post type (e.g., `--type compare` or `--type product`). The `WordPressPublisher.create_draft()` method dynamically constructs the REST endpoint URL using this parameter, posting to `/wp-json/wp/v2/{post_type}`. Note that the custom post type must already be registered in WordPress and support the REST API for this functionality to work correctly.

### What happens if a category or tag doesn't exist in WordPress?

The `WordPressPublisher` class automatically creates missing taxonomies before publishing. The `get_or_create_category()` and `get_or_create_tag()` methods first query the WordPress REST API to check for existing terms by name. If a term is not found, the system sends a POST request to the appropriate endpoint (`/wp-json/wp/v2/categories` or `/wp-json/wp/v2/tags`) to create the taxonomy term dynamically. This ensures that drafts can reference new categories or tags without manual pre-configuration in the WordPress admin.