> ## Documentation Index
> Fetch the complete documentation index at: https://developers.semji.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect WordPress to Semji

> Step-by-step guide to connect your WordPress site to Semji with an application password, so AI agents can list, read, create, and update your content — posts, pages, and custom post types, with taxonomies, ACF fields, and SEO titles and meta descriptions via Yoast SEO, Rank Math, or SEOPress.

Semji connects to WordPress through the WordPress REST API (`/wp-json/`) using an **application password**. Once connected, Semji's AI agents can list, read, create, and update content on your site — built-in posts and pages as well as custom post types (FAQ, news, landing pages, …) — fill your Advanced Custom Fields (ACF), file articles in the right categories, tags, and custom taxonomies, and update your SEO titles and meta descriptions whatever your SEO plugin (Yoast SEO, Rank Math, or SEOPress). Each action can be toggled individually.

Compatible from **WordPress 5.6**.

## Prerequisites

* **WordPress 5.6 or newer** — application passwords became a core feature in 5.6.
* A site served over **HTTPS** and reachable from the **public internet**. Private or reserved addresses (localhost, `10.x`, `192.168.x`, …) are rejected.
* The **REST API enabled** at `https://your-site.com/wp-json/`. It is on by default, but some security plugins disable it.
* A WordPress user allowed to manage posts (**Author** role or higher — use **Editor** if agents should be able to update any post).
* If a firewall, WAF, or anti-bot protection (Cloudflare, Akamai, Sucuri, …) sits in front of your site, it must **allow Semji's IP addresses** — see [Firewalls, Cloudflare, and IP allowlisting](#firewalls-cloudflare-and-ip-allowlisting).
* You are a **workspace owner** in Semji. The connection is shared with the whole workspace.

<Note>
  Availability of the WordPress integration depends on your Semji plan. If the
  **Connect** button opens an upgrade dialog instead of the connection form,
  contact your account manager.
</Note>

## Step 1 — Create an application password in WordPress

<Steps>
  <Step title="Open the user profile">
    In wp-admin, go to **Users → Profile** (or **Users → All Users** and open
    the account Semji will use).
  </Step>

  <Step title="Create the application password">
    Scroll down to the **Application Passwords** section, enter a name such as
    `Semji`, and click **Add New Application Password**.
  </Step>

  <Step title="Copy the generated password">
    WordPress displays the password **only once**, formatted like
    `xxxx xxxx xxxx xxxx xxxx xxxx`. Copy it now — you can paste it into Semji
    with or without the spaces.
  </Step>
</Steps>

<Note>
  Don't see the **Application Passwords** section? Your site is either not
  served over HTTPS, running a WordPress version older than 5.6, or a security
  plugin has disabled the feature.
</Note>

## Step 2 — Connect from Semji

<Steps>
  <Step title="Open the CMS integrations">
    In Semji, go to **Settings → Integrations**, open the **CMS** tab, and
    click **Connect** on the WordPress row.
  </Step>

  <Step title="Enter your credentials">
    Fill in the three fields:

    * **Site URL** — the canonical HTTPS URL of your site, e.g.
      `https://your-site.com`. Semji does not follow redirects, so use the
      exact URL your site resolves to (`www` vs non-`www` matters — see
      [troubleshooting](#troubleshooting)).
    * **Email or username** — the WordPress login or email address of the
      user who owns the application password. WordPress accepts either: it
      matches the username first, then falls back to the email address.
    * **Application password** — the password generated in step 1.

    Click **Next**. Semji tests the connection live by calling
    `GET /wp-json/wp/v2/users/me` on your site with these credentials, and
    detects which SEO plugin your site runs.
  </Step>

  <Step title="Pick the WordPress tools">
    Choose what AI agents can do on your site. All six tools are enabled by
    default: **Create posts**, **Read posts**, **List post types**, **List
    posts**, **List taxonomy terms**, and **Update posts**. You can change
    this selection at any time later.

    **Update posts** also covers the SEO title and meta description, which
    may need a prerequisite configuration on your WordPress site depending on
    your SEO plugin — see [Enable SEO title and meta description
    updates](#enable-seo-title-and-meta-description-updates). And if your
    content types use ACF fields, expose them to the REST API so agents can
    read and fill them — see [Expose ACF fields to
    Semji](#expose-acf-fields-to-semji).
  </Step>

  <Step title="Activate">
    Click **Activate**. The WordPress row now shows as connected.
  </Step>
</Steps>

## What Semji can do once connected

| Tool                | What it does                                                                                                                                                                                                                                                                                                              |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| List post types     | Discover the site's content types — built-in posts and pages plus custom post types (FAQ, news, …) with the taxonomies attached to each — so agents publish in the right place                                                                                                                                            |
| List posts          | Search and list posts of any content type, with status filters and pagination                                                                                                                                                                                                                                             |
| Read posts          | Fetch a post's full content: raw editor source, SEO title and meta description, taxonomy terms, and ACF fields when the site exposes them                                                                                                                                                                                 |
| Create posts        | Create new posts on any content type — always as **draft** unless you explicitly ask an agent to publish — with categories, tags, custom taxonomy terms, and ACF fields                                                                                                                                                   |
| Update posts        | Update only the fields you change: title, content, status, slug, featured image, categories, tags, custom taxonomy terms, ACF fields, SEO title, and meta description (the SEO fields go through your SEO plugin — may require a prerequisite configuration, see [below](#enable-seo-title-and-meta-description-updates)) |
| List taxonomy terms | Search and list the terms of any taxonomy — categories with their hierarchy, tags, or custom taxonomies (authors, thematics, …) — so agents resolve term names to IDs before filing an article                                                                                                                            |

## Supported SEO plugins

Semji detects your SEO plugin automatically — no configuration to pick a
provider. Detection reads the namespaces advertised by your REST API index
(`/wp-json/`), and falls back to inspecting the SEO fields exposed on your
posts when a security plugin filters the index. The SEO title and meta
description are then read and written through the detected plugin's own
fields:

| SEO plugin | SEO title field          | Meta description field  |
| ---------- | ------------------------ | ----------------------- |
| Yoast SEO  | `_yoast_wpseo_title`     | `_yoast_wpseo_metadesc` |
| Rank Math  | `rank_math_title`        | `rank_math_description` |
| SEOPress   | `_seopress_titles_title` | `_seopress_titles_desc` |

<Note>
  **All in One SEO is not supported.** It stores its metadata in a custom
  database table instead of standard post fields, so it cannot be updated
  through the WordPress REST API. On AIOSEO sites, SEO updates report an
  explicit warning while the rest of the post update still applies;
  everything else in the integration works normally.
</Note>

<Warning>
  If several SEO plugins are active at the same time (typically during a
  migration), Semji picks the first supported one in this order: Yoast SEO,
  then Rank Math, then SEOPress. Deactivate the plugin you no longer use so
  updates target the one that actually renders your pages.
</Warning>

## Enable SEO title and meta description updates

Whether a prerequisite is needed depends on your SEO plugin and its version:

* **Yoast SEO 28.1 and newer** — nothing to do. Recent Yoast versions expose
  their SEO title and meta description fields to the REST API out of the box,
  and Semji can update them right away.
* **Older Yoast SEO, Rank Math, and SEOPress** — these store the SEO title and
  meta description in protected custom fields that the REST API does not
  accept writes on by default. A small PHP snippet must register the fields
  with the REST API first.

There are two equivalent ways to install the snippet — entirely from the
WordPress admin with a snippet plugin (no server access needed), or as a
**must-use plugin** file dropped on the server. Pick one. The same snippet
covers all three supported SEO plugins and every public post type (posts,
pages, and custom post types alike), so it keeps working if you later switch
SEO plugins or add content types.

Writes remain protected by WordPress permissions: only users allowed to edit
posts (the `edit_posts` capability) can modify the fields — the same
permission level as editing the post itself.

<Tabs>
  <Tab title="From the WordPress admin">
    Use a snippet plugin such as [Code
    Snippets](https://wordpress.org/plugins/code-snippets/) — it stores the
    code in the database and runs it on every load, so nothing touches the
    server's filesystem.

    <Steps>
      <Step title="Install the plugin">
        In wp-admin, go to **Plugins → Add New Plugin**, search for **Code
        Snippets**, then install and activate it.
      </Step>

      <Step title="Create the snippet">
        In the new **Snippets** menu, click **Add New**, give the snippet a
        name such as `Semji — SEO fields REST support`, and paste the code
        below. It starts without the opening `<?php` line — Code Snippets adds
        it itself.

        ```php theme={null}
        /**
         * Plugin Name: Semji — SEO fields REST support
         * Description: Registers the SEO title and meta description fields of
         * the active SEO plugin (Yoast SEO, Rank Math, or SEOPress) with the
         * WordPress REST API so they can be read and updated by authorized
         * applications, on every public post type. Writing requires the
         * edit_posts capability.
         */

        add_action('init', function () {
            // Each pair of fields is registered only while its plugin is active,
            // so an inactive plugin's fields are never exposed by mistake.
            $seo_meta_keys_by_plugin_constant = [
                'WPSEO_VERSION'      => ['_yoast_wpseo_title', '_yoast_wpseo_metadesc'],
                'RANK_MATH_VERSION'  => ['rank_math_title', 'rank_math_description'],
                'SEOPRESS_VERSION'   => ['_seopress_titles_title', '_seopress_titles_desc'],
            ];

            foreach ($seo_meta_keys_by_plugin_constant as $plugin_constant => $seo_meta_keys) {
                if (!defined($plugin_constant)) {
                    continue;
                }

                foreach (get_post_types(['public' => true], 'names') as $post_type) {
                    foreach ($seo_meta_keys as $meta_key) {
                        register_post_meta($post_type, $meta_key, [
                            'type'          => 'string',
                            'single'        => true,
                            'show_in_rest'  => true,
                            'auth_callback' => static function () {
                                return current_user_can('edit_posts');
                            },
                        ]);
                    }
                }
            }
        }, 20);
        ```

        The snippet detects which of the three plugins is active and only
        registers that plugin's fields, so it keeps working if you later
        switch SEO plugins.
      </Step>

      <Step title="Run it everywhere and activate">
        Keep the scope on **Run snippet everywhere** — REST API requests must
        load it too, so do not restrict it to the admin area — then click
        **Save Changes and Activate**.
      </Step>
    </Steps>

    Deactivating the snippet removes the capability.
  </Tab>

  <Tab title="On the server">
    A must-use plugin is a single PHP file dropped into
    `wp-content/mu-plugins/`. Must-use plugins are loaded automatically by
    WordPress — there is nothing to activate, and removing the file removes
    the capability.

    <Steps>
      <Step title="Create the plugin file">
        On your server, create the directory `wp-content/mu-plugins/` if it
        does not exist yet, then create a file named
        `semji-seo-fields.php` inside it.
      </Step>

      <Step title="Paste the plugin code">
        ```php semji-seo-fields.php theme={null}
        <?php
        /**
         * Plugin Name: Semji — SEO fields REST support
         * Description: Registers the SEO title and meta description fields of
         * the active SEO plugin (Yoast SEO, Rank Math, or SEOPress) with the
         * WordPress REST API so they can be read and updated by authorized
         * applications, on every public post type. Writing requires the
         * edit_posts capability.
         */

        add_action('init', function () {
            // Each pair of fields is registered only while its plugin is active,
            // so an inactive plugin's fields are never exposed by mistake.
            $seo_meta_keys_by_plugin_constant = [
                'WPSEO_VERSION'      => ['_yoast_wpseo_title', '_yoast_wpseo_metadesc'],
                'RANK_MATH_VERSION'  => ['rank_math_title', 'rank_math_description'],
                'SEOPRESS_VERSION'   => ['_seopress_titles_title', '_seopress_titles_desc'],
            ];

            foreach ($seo_meta_keys_by_plugin_constant as $plugin_constant => $seo_meta_keys) {
                if (!defined($plugin_constant)) {
                    continue;
                }

                foreach (get_post_types(['public' => true], 'names') as $post_type) {
                    foreach ($seo_meta_keys as $meta_key) {
                        register_post_meta($post_type, $meta_key, [
                            'type'          => 'string',
                            'single'        => true,
                            'show_in_rest'  => true,
                            'auth_callback' => static function () {
                                return current_user_can('edit_posts');
                            },
                        ]);
                    }
                }
            }
        }, 20);
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

### Verify the fields are exposed

Whichever option you chose, call your REST API with the same credentials
Semji uses, on any post ID, checking your plugin's meta description field
(here Rank Math — swap in the field from the [table
above](#supported-seo-plugins) for Yoast SEO or SEOPress):

```bash theme={null}
curl -s -u 'USERNAME:APP_PASSWORD' \
  'https://your-site.com/wp-json/wp/v2/posts/123' | jq '.meta.rank_math_description'
```

A string — even an empty one (`""`) — means the field is exposed and Semji
can update it. `null` means the code is not running yet.

<Note>
  Without this prerequisite configuration, the SEO part of an **Update posts**
  call is skipped: the rest of the post update still applies, and the agent
  receives an explicit warning naming your SEO plugin and the missing field —
  agents will tell you the field is not exposed rather than pretend the update
  succeeded. Everything else in the integration works normally.
</Note>

## Expose ACF fields to Semji

Many WordPress sites structure their content with [Advanced Custom
Fields](https://www.advancedcustomfields.com/) (ACF) — for example a FAQ
custom post type whose question and answer live in dedicated fields rather
than in the post body. Semji's agents handle those fields natively: **Read
posts** returns a post's ACF values, and **Create posts** / **Update posts**
fill them.

This only works for fields visible through the REST API, and **ACF does not
expose field groups by default**: out of the box the `acf` key is absent from
your site's REST responses, so agents cannot see the fields — let alone fill
them — and will tell you so during a publication.

Unlike the SEO fields above, no PHP snippet is needed: ACF (free and Pro,
version 5.11 or newer) ships native REST API support behind a single setting.
The **Secure Custom Fields** fork offers the same setting.

### Enable "Show in REST API" on the field group

The setting lives on the **field group**, not on each individual field:

<Steps>
  <Step title="Open the field group">
    In wp-admin, go to **ACF → Field Groups** and open the group attached to
    your content type (e.g. *FAQ*).
  </Step>

  <Step title="Enable Show in REST API">
    In the field group's **Settings** panel, turn on **Show in REST API**,
    then save the group.
  </Step>

  <Step title="Repeat for each field group">
    Do the same for every field group agents should be able to read and
    fill.
  </Step>
</Steps>

<Note>
  For a **custom post type**, the type itself must also be visible in the
  REST API — otherwise agents cannot even list it. If it was created through
  ACF's **Post Types** UI, enable **Show in REST API** in the post type's
  **Advanced settings**; if it is registered in code, pass
  `'show_in_rest' => true` to `register_post_type()`.
</Note>

### Verify the fields are exposed

Call your REST API with the same credentials Semji uses, on any existing item
— swap `faq` for the rest base of your content type (`posts` for built-in
posts):

```bash theme={null}
curl -s -u 'USERNAME:APP_PASSWORD' \
  'https://your-site.com/wp-json/wp/v2/faq/123' | jq '.acf'
```

An object listing your fields — even with empty values — means they are
exposed and agents can read and fill them. `null` means the field group is
not exposed yet.

### Help agents fill the fields correctly

The REST API carries only field **names** and values: the labels and
instructions you see in wp-admin stay invisible to agents. Agents discover a
type's fields by reading an existing item, so a few habits make the fills
reliable:

* **Use descriptive field names** — an agent understands `answer_short`;
  it can only guess at `field_2`.
* **Keep at least one well-filled item** of each content type: agents read
  it to learn which field holds what, and the expected value shape (plain
  text vs HTML, media ID for an image field, …).
* **Brief the agent** — when field names are ambiguous, say what each field
  is for in your instructions; the agent will map its content accordingly.

## Manage or disconnect

From **Settings → Integrations → CMS**, the WordPress row offers:

* **Manage tools** — enable or disable each of the six tools.
* **Disconnect** — remove the integration for the whole workspace.

## Firewalls, Cloudflare, and IP allowlisting

Semji's servers call your WordPress REST API directly. If your site sits
behind Cloudflare or another WAF, you must allow those calls or the
connection will fail.

Semji's servers reach the internet through a fixed set of outbound IP addresses. If a firewall, WAF, or anti-bot protection (Cloudflare, Akamai, Sucuri, …) sits in front of your site, it can block Semji's requests — typically with a `403 Forbidden` — before they ever reach your CMS, even when your credentials are correct.

Allow the following Semji IP addresses:

```text theme={null}
63.34.75.122
63.35.78.179
54.228.104.165
18.200.156.37
34.248.117.83
52.213.28.177
```

On **Cloudflare**, create a WAF custom rule with the **Skip** action matching
requests where the source IP is one of the addresses above and the path
starts with `/wp-json/`.

<Warning>
  A WAF block is indistinguishable from bad credentials: when a protection
  layer answers `403 Forbidden` on `/wp-json/`, Semji shows **"Invalid
  WordPress credentials"** even though your login and application password
  are correct. If you are sure of the credentials, check the firewall first.
</Warning>

## Troubleshooting

<AccordionGroup>
  <Accordion title="&#x22;Invalid WordPress credentials. Check the email or username and the application password.&#x22;">
    Your site answered `401` or `403`. Either the login (email or username)
    or application password is wrong or was revoked, application passwords are disabled on
    the site, or — very commonly — a WAF or anti-bot layer (Cloudflare,
    Wordfence, …) is blocking Semji before the request reaches WordPress. See
    [Firewalls, Cloudflare, and IP
    allowlisting](#firewalls-cloudflare-and-ip-allowlisting).
  </Accordion>

  <Accordion title="&#x22;Could not connect to the WordPress site. Check credentials and site URL.&#x22;">
    Semji could not complete the request. Frequent causes:

    * **The URL redirects.** Semji follows no redirects: if
      `https://example.com` redirects to `https://www.example.com`, enter the
      `www` URL. Same for HTTP→HTTPS or trailing-path redirects.
    * **The REST API is not served** — `/wp-json/` returns 404 (REST API
      disabled by a plugin, or permalink/rewrite issues on the server).
    * **The site is unreachable** — DNS failure, host down, or the domain
      resolves to a private network address.
    * **The site is too slow** — responses must arrive within 10 seconds.
  </Accordion>

  <Accordion title="URL validation errors before submitting">
    * *"Please enter a valid URL."* — the value does not parse as a URL.
    * *"The site URL must start with https\://."* — HTTP sites cannot be
      connected.
    * *"The site URL points to a private or reserved address, which is not
      allowed."* — localhost and private-network hosts are rejected; the site
      must be publicly reachable.
  </Accordion>

  <Accordion title="An agent reports a WordPress error after the connection worked">
    * *"WordPress authentication failed. Please check the credentials in the
      integration settings."* — the application password was revoked or
      changed since connection. Reconnect with a fresh one.
    * *"No WordPress integration configured for this workspace."* — the
      integration was disconnected; a workspace owner must reconnect it.
    * *"The post was updated, but its SEO metadata was NOT: no supported SEO
      plugin (Yoast SEO, Rank Math or SEOPress) was detected…"* — Semji found
      neither Yoast SEO, Rank Math, nor SEOPress on the site. Activate one of
      them, or if one is already active, apply the [prerequisite
      configuration](#enable-seo-title-and-meta-description-updates) so its
      fields are visible through the REST API.
    * *"The SEO metadata was NOT saved: this WordPress site runs … but does
      not expose its … field(s) for writing through the REST API…"* — the
      site does not accept REST API writes on your SEO plugin's fields yet.
      Apply the prerequisite configuration described in [Enable SEO title and
      meta description
      updates](#enable-seo-title-and-meta-description-updates). The rest of
      the post update was applied.
  </Accordion>

  <Accordion title="An agent says it cannot see or fill the ACF fields of a content type">
    The field group is not exposed to the REST API, so the `acf` key is
    absent from your site's REST responses and agents cannot read or write
    those fields. Enable **Show in REST API** on the field group — see
    [Expose ACF fields to Semji](#expose-acf-fields-to-semji). If the content
    type itself does not show up when the agent lists post types, the post
    type also needs to be visible in the REST API (same section).
  </Accordion>
</AccordionGroup>

## Related

* [Sync drafts to your CMS](/guides/sync-drafts-to-cms) — API-based publishing flow, if you prefer to drive the sync yourself.
