# How wordpress_publisher.py Manages Yoast SEO Metadata via the Custom MU-Plugin

> Discover how wordpress_publisher.py automates Yoast SEO metadata using a custom MU-plugin and the WordPress REST API. Streamline your SEO management effortlessly.

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

---

**The [`wordpress_publisher.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/wordpress_publisher.py) module automates Yoast SEO metadata by sending a structured JSON payload to a custom WordPress MU-plugin that registers private Yoast meta keys with the REST API.**

The SEO Machine project (`TheCraigHewitt/seomachine`) provides a complete Python-to-WordPress publishing pipeline. The `WordPressPublisher` class in [`data_sources/modules/wordpress_publisher.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/data_sources/modules/wordpress_publisher.py) handles content creation and integrates deeply with Yoast SEO by leveraging a custom must-use plugin located at [`wordpress/seo-machine-yoast-rest.php`](https://github.com/TheCraigHewitt/seomachine/blob/main/wordpress/seo-machine-yoast-rest.php).

## Understanding the Two-Component Architecture

The solution relies on a coordinated pair of components:

- **Python Client ([`wordpress_publisher.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/wordpress_publisher.py))**: Parses markdown drafts, creates WordPress posts via the REST API, and formats SEO metadata into a specific JSON structure.
- **WordPress MU-Plugin ([`seo-machine-yoast-rest.php`](https://github.com/TheCraigHewitt/seomachine/blob/main/seo-machine-yoast-rest.php))**: Runs on the WordPress installation to expose private Yoast meta keys (`_yoast_wpseo_title`, `_yoast_wpseo_metadesc`, `_yoast_wpseo_focuskw`) through a custom REST field named `yoast_seo`.

This architecture avoids direct database manipulation by using WordPress's official REST API, ensuring compatibility with application password authentication and standard WordPress security models.

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

The MU-plugin performs two critical registration steps during the WordPress `init` hook:

1. **Meta Key Registration**: It iterates through Yoast's private meta keys and explicitly registers them with `'show_in_rest' => true`, making them readable via standard REST responses.

2. **Custom Field Registration**: It creates a composite field `yoast_seo` using `register_rest_field()` that maps three user-friendly keys (`seo_title`, `meta_description`, `focus_keyphrase`) to the underlying Yoast meta keys.

When the Python client sends a `POST` request to `/wp-json/wp/v2/posts/{id}` with a `yoast_seo` object in the JSON body, the MU-plugin's `update_callback` function receives the data and writes it to the appropriate post meta fields using `update_post_meta()`.

## The Publishing Flow in wordpress_publisher.py

When `publish_draft()` is invoked, the module executes a sequential workflow:

1. **Parse Markdown**: Extracts frontmatter including `Meta Title`, `Meta Description`, and `Target Keyword`.
2. **Create Draft**: Calls `create_draft()` to send a `POST` request to `/wp-json/wp/v2/{post_type}` with the post content, receiving a `post_id` in response.
3. **Inject Yoast Metadata**: If SEO fields are present, calls `set_yoast_meta()` with the `post_id` and the extracted metadata values.

This separation of concerns allows the draft creation to succeed independently while ensuring SEO metadata is applied as a discrete update operation.

## The set_yoast_meta() Method Explained

The `set_yoast_meta()` method constructs a specific payload that the MU-plugin expects. Located at lines 312-350 in [`wordpress_publisher.py`](https://github.com/TheCraigHewitt/seomachine/blob/main/wordpress_publisher.py), it formats the REST request:

```python
def set_yoast_meta(
    self,
    post_id: int,
    meta_title: str,
    meta_description: str,
    focus_keyphrase: str,
    post_type: str = 'posts'
) -> Dict:
    """
    Set Yoast SEO meta fields on a post, page, or custom post type
    """
    # Build payload matching the mu-plugin's custom field

    yoast_data = {
        'yoast_seo': {
            'seo_title': meta_title,
            'meta_description': meta_description,
            'focus_keyphrase': focus_keyphrase
        }
    }

    response = self.session.post(
        f"{self.api_base}/{post_type}/{post_id}",
        json=yoast_data
    )
    response.raise_for_status()
    return response.json()

```

**Key implementation details:**

- **Payload Structure**: The `yoast_seo` key must match exactly what the MU-plugin registered via `register_rest_field()`.
- **HTTP Method**: Uses `POST` (which WordPress treats as a partial update when an ID is specified in the URL) to the standard posts endpoint.
- **Authentication**: Relies on the `session` object initialized with WordPress application password credentials.

## Practical Implementation Example

Here is a complete workflow for publishing a markdown draft with Yoast SEO metadata:

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

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

publisher = WordPressPublisher()

# Publish draft and inject Yoast metadata automatically

result = publisher.publish_draft(
    file_path='content/optimized-article.md',
    post_type='post'  # or 'page', 'custom_type'

)

# Output confirms WordPress IDs and Yoast fields

print(f"Published Post ID: {result['post_id']}")
print(f"WordPress Edit URL: {result['edit_url']}")
print("Yoast SEO Settings Applied:")
print(f"  SEO Title: {result['meta']['title']}")
print(f"  Meta Description: {result['meta']['description']}")
print(f"  Focus Keyphrase: {result['meta']['focus_keyphrase']}")

```

The underlying HTTP request sent to WordPress looks like this:

```http
POST /wp-json/wp/v2/posts/456
Authorization: Basic dXNlcm5hbWU6YXBwX3Bhc3N3b3Jk
Content-Type: application/json

{
  "yoast_seo": {
    "seo_title": "Complete Guide to Python SEO Automation",
    "meta_description": "Learn how to automate Yoast SEO metadata using Python and the WordPress REST API.",
    "focus_keyphrase": "python seo automation"
  }
}

```

## Summary

- **wordpress_publisher.py** automates Yoast SEO by sending structured JSON to a custom WordPress endpoint.
- The **MU-plugin** ([`seo-machine-yoast-rest.php`](https://github.com/TheCraigHewitt/seomachine/blob/main/seo-machine-yoast-rest.php)) bridges the gap between Python and Yoast by exposing private meta keys through a REST field named `yoast_seo`.
- The **`set_yoast_meta()`** method constructs the exact payload expected by the plugin, updating `_yoast_wpseo_title`, `_yoast_wpseo_metadesc`, and `_yoast_wpseo_focuskw` in a single request.
- This integration requires no database access or file manipulation beyond standard WordPress REST API authentication.

## Frequently Asked Questions

### How does the MU-plugin handle authentication for REST API requests?

The MU-plugin relies entirely on WordPress core's REST API authentication mechanisms. Because it registers fields using `register_rest_field()` and `register_meta()` with `'show_in_rest' => true`, it inherits the same permission checks as standard post endpoints. Requests must include valid WordPress application password credentials in the Authorization header, or use cookie-based authentication for logged-in users.

### Can I use wordpress_publisher.py with custom post types?

Yes. The `publish_draft()` and `set_yoast_meta()` methods accept a `post_type` parameter that defaults to `'posts'` but can be set to any custom post type slug (e.g., `'product'`, `'portfolio'`). The MU-plugin automatically registers the `yoast_seo` field for all post types that have REST API support enabled, ensuring Yoast metadata can be written to custom types without additional configuration.

### What happens if the Yoast SEO plugin is not installed on the WordPress site?

If the Yoast SEO plugin is missing, the MU-plugin will still register the meta keys and accept REST requests containing `yoast_seo` data. However, because the underlying Yoast plugin is not present to interpret these meta values (`_yoast_wpseo_title`, `_yoast_wpseo_metadesc`, `_yoast_wpseo_focuskw`), the metadata will be stored in the WordPress database but will not appear in the Yoast SEO meta box or affect SEO output on the frontend. For full functionality, both the Yoast SEO plugin and the MU-plugin must be active.