# WordPress Integration

> Push WordPress posts and pages into a Reaktly knowledge base.

URL: https://docs.reaktly.com/docs/integrations/wordpress

## Overview

Two ways to get WordPress content into Reaktly:

1. **Push from WordPress** — your site forwards each post to Reaktly when it is saved (recommended: updates arrive immediately)
2. **Pull with a script** — a scheduled job reads the WordPress REST API and pushes

Both end in the same place: items in your knowledge base, classified as `ARTICLE` ([Source types](/docs/integrations/source-types)).

> **Note:**
> A packaged WordPress plugin is on the roadmap. Until it ships, the two approaches below are the supported paths — both are small and self-contained.

## Option 1: Push from WordPress

Reaktly exposes a WordPress endpoint that accepts a post object (or an array of them):

```
POST https://api.reaktly.com/integrations/wordpress/sync
x-api-key: YOUR_API_KEY
```

Add a small mu-plugin to your theme's `functions.php` (or better, `wp-content/mu-plugins/`):

```php
<?php
add_action('save_post', function ($post_id) {
    if (wp_is_post_revision($post_id) || get_post_status($post_id) !== 'publish') {
        return;
    }

    $post = get_post($post_id);

    wp_remote_post('https://api.reaktly.com/integrations/wordpress/sync', [
        'headers' => [
            'Content-Type' => 'application/json',
            'x-api-key'    => 'YOUR_API_KEY',
        ],
        'body' => wp_json_encode([
            'id'      => (string) $post->ID,
            'title'   => ['rendered' => get_the_title($post)],
            'content' => ['rendered' => apply_filters('the_content', $post->post_content)],
            'link'    => get_permalink($post),
        ]),
        'timeout' => 15,
    ]);
});
```

Every published save re-syncs that post. Send an array to sync several posts in one call.

## Option 2: Pull with a script

Use the WordPress REST API when you prefer a scheduled job (for example an initial import):

```python
import hashlib
import requests

WP_URL = "https://your-site.com/wp-json/wp/v2"
REAKTLY_API = "https://api.reaktly.com"
API_KEY = "your-api-key"

def sync_posts():
    posts = requests.get(f"{WP_URL}/posts?per_page=100").json()

    items = [{
        "integrationId": "wp-integration-id",
        "sourceType": "ARTICLE",
        "externalId": f"wp-post-{post['id']}",
        "data": {
            "title": post["title"]["rendered"],
            "content": post["content"]["rendered"],
            "url": post["link"],
            "hash": hashlib.sha256(post["modified"].encode()).hexdigest(),
        },
    } for post in posts]

    response = requests.post(
        f"{REAKTLY_API}/ingest/bulk",
        json={"items": items},
        headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
        timeout=30,
    )
    response.raise_for_status()
    print(response.json()["jobCount"], "items queued")
```

The `hash` field makes the job cheap: unchanged posts are skipped ([Change detection](/docs/integrations/change-detection)).

> **Warning:**
> `content.rendered` arrives as HTML. Reaktly normalises it during processing, but you get cleaner answers when you strip navigation, footers, and shortcodes before sending — send `post_content` rather than a rendered page.

## Pages and custom post types

The push endpoint accepts any post-shaped object with an `id`. For pages, use `/wp-json/wp/v2/pages` in the script variant, or the same `save_post` hook filtered to your post types. Give each type a distinct `sourceOrigin` (e.g. `wordpress-posts`, `wordpress-pages`) so [cleanup after a full sync](/docs/api-reference/ingestion#post-ingestcleanup--remove-orphaned-items) stays scoped.