WP Manifestindependent plugin directory
manifest / ai / claude-connector

Claude Connector self-updates

Secure REST API bridge connecting Claude AI to any WordPress site — no cPanel, SSH, or hosting access needed

by Wisnuub · github.com/wisnuub/claude-connector · website

1stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/wisnuub/claude-connector/archive/refs/heads/main.zip

Ships its own WordPress updater (built-in updater), so new versions show up under Dashboard → Updates.

A WordPress plugin that gives Claude Code (or any Claude AI agent) full programmatic access to a WordPress site - without needing cPanel, SSH, or hosting credentials.

Install it once on any WordPress site. Then tell Claude:

"Connect to example.com with key abc123..."

Claude can then manage ACF field groups, flush caches, read and write theme files, query the database, create posts, and more - all through a secure REST API.


Why this exists

When working with WordPress agencies or freelancers, developers often need access to a client's site to:

  • Sync ACF field group JSON after uploading files
  • Flush the page cache after deploying changes
  • Read theme files to understand the current structure
  • Create or update posts and options programmatically

Normally this requires cPanel access, SSH keys, or asking the client to do it manually. This plugin removes that friction entirely - the developer installs the plugin, shares the API key, and the AI agent handles the rest.


Installation

Option A - Upload zip (recommended)

  1. Download claude-connector.zip from the Releases page
  2. WP Admin → Plugins → Add New → Upload Plugin
  3. Upload the zip → Install NowActivate

Option B - Manual

  1. Copy the claude-connector/ folder to /wp-content/plugins/
  2. Activate it from WP Admin → Plugins

Auto-updates

The plugin isn't listed on wordpress.org, so it checks GitHub Releases directly instead. Once a newer tag is published, WP Admin → Plugins shows the usual "update available" notice and Update Now installs it - no manual zip re-upload needed.

Release checks are cached for 12 hours. To ship a new version: bump Version in the plugin header, tag the commit (vX.Y.Z), and attach a claude-connector.zip build to the GitHub release.


Connecting Claude Code (one-click setup)

WP Admin → Settings → Claude Connector → Connect Claude Code shows a download button for your OS (detected automatically, both shown if detection fails):

  • Mac - double-click the downloaded .command file in Finder
  • Windows - right-click the downloaded .ps1 file → Run with PowerShell

The script installs the MCP bridge, creates a workspace folder for the site, writes .mcp.json + CLAUDE.md, and opens the folder in VSCode. Requires Node.js and VSCode with the Claude Code extension.


Shared knowledge base

Every generated CLAUDE.md tells Claude to check a running knowledge base (KNOWLEDGE.md) before troubleshooting an unfamiliar WordPress/Elementor/Divi/hosting issue, and to record a fix there after solving something non-obvious - so the next site doesn't start from zero. This is two MCP tools, not a WordPress REST endpoint, so it works the same regardless of which site you're connected to:

  • wp_knowledge_search - reads KNOWLEDGE.md straight from GitHub (public, no auth needed) and returns matching entries.
  • wp_knowledge_add - records a new entry. What happens depends on your GitHub CLI (gh) login:
    • Not installed / not logged in - the entry is saved locally only; Claude will tell you to run gh auth login if you want fixes shared.
    • Logged in, no push access to this repo - the entry is queued locally and opened as a single GitHub issue summarizing everything queued so far, at most once per calendar day (checked whenever a new finding comes in, not on a background timer).
    • Logged in with push access (the maintainer) - committed straight to KNOWLEDGE.md.

No GitHub token is stored anywhere - gh handles its own authentication, and a duplicate check against existing entries runs before every write to avoid spamming the repo with near-identical findings.


Configuration

After activation, go to WP Admin → Settings → Claude Connector to find your API key and the base URL.

Optional: pin the key in wp-config.php

// wp-config.php
define( 'CLAUDE_API_KEY', 'your-64-char-hex-key-here' );

This prevents the key from changing if the database is reset and is the recommended approach for long-term projects.


Connecting Claude to a site

Give Claude the site URL and API key. No other credentials are needed.

Example prompt:

Connect to example.com
API key: a3f8c2d1e9b4...

Claude will use https://example.com/wp-json/claude/v1/ as the base URL and authenticate every request with X-Claude-Key: <key>.


API Reference

All endpoints are under /wp-json/claude/v1/. Every request must include the header:

X-Claude-Key: <your-api-key>

Status

GET /status

Returns site info: WP version, PHP version, active theme, active plugins, timezone, etc.


ACF

GET  /acf/groups
POST /acf/sync

GET /acf/groups - list all field groups with their key, title, active status, and field count.

POST /acf/sync - sync field groups from local JSON files (same as clicking "Sync" in ACF → Field Groups).

// POST /acf/sync
// Sync all groups:
{}

// Sync specific groups only:
{ "groups": ["group_abc123", "group_def456"] }

Elementor

GET  /elementor/widgets
GET  /elementor/data/{id}
POST /elementor/data/{id}

Lets Claude build and edit pages using Elementor's own native widget/module format (_elementor_data), instead of writing raw HTML into post_content.

GET /elementor/widgets - lists every registered widget type on this site (stock Elementor, Elementor Pro, and any third-party addon widgets) with its editable settings/control schema, so Claude uses real field names instead of guessing.

GET /elementor/data/{id} - returns the decoded elements tree for a post plus edit-mode/version meta.

POST /elementor/data/{id} - writes an elements tree and clears Elementor's CSS cache so the change renders immediately.

// POST /elementor/data/42
{
  "elements": [
    {
      "id": "a1b2c3d",
      "elType": "section",
      "elements": [
        {
          "id": "e4f5g6h",
          "elType": "column",
          "elements": [
            { "id": "i7j8k9l", "elType": "widget", "widgetType": "heading", "settings": { "title": "Hello" } }
          ]
        }
      ]
    }
  ]
}

Requires the Elementor plugin to be active; returns 422 otherwise.


Divi

GET  /divi/modules
GET  /divi/data/{id}
POST /divi/data/{id}

Same idea as the Elementor endpoints, for Divi. Divi has two generations with different content formats - classic Divi (shortcodes in post_content) and Divi 5 (a newer structured module model) - so responses include a generation field (d4_shortcode or d5_json). Module schema discovery is currently only wired up for classic Divi; Divi 5 support is best-effort and may need adjusting against a live site.

GET /divi/modules - lists known module types for the detected generation.

GET /divi/data/{id} - returns post_content plus Divi builder meta and the detected generation.

POST /divi/data/{id} - writes builder content. content must already match the site's detected generation's format (shortcode markup for classic Divi, module JSON for Divi 5).

Requires Divi to be active; returns 422 otherwise.


Cache

POST /cache/purge

Flushes all available caches automatically: WP object cache, transients, WP Engine page cache, W3 Total Cache, WP Super Cache, WP Rocket, and LiteSpeed Cache.


Posts

GET    /posts
POST   /posts
GET    /posts/{id}
PUT    /posts/{id}
DELETE /posts/{id}

Query posts:

GET /posts?type=service&status=publish&search=cyber&per_page=10&page=1

Create a post:

POST /posts
{
  "post_type":    "service",
  "post_title":   "Cyber Insurance",
  "post_content": "<p>Content here</p>",
  "post_status":  "publish",
  "meta_input":   { "custom_field": "value" }
}

Update a post:

PUT /posts/42
{
  "post_title": "Updated Title",
  "post_status": "publish"
}

GET /posts/{id} returns the post with all meta fields and taxonomy terms included.


Options

GET  /options?key=<option_name>
POST /options
// Write an option:
POST /options
{ "key": "my_plugin_setting", "value": { "enabled": true } }

Some options are protected and cannot be written: siteurl, home, active_plugins, WordPress secret keys, and the connector's own API key.


Plugins

GET  /plugins
POST /plugins
// Activate a plugin:
POST /plugins
{ "plugin": "advanced-custom-fields/acf.php", "action": "activate" }

// Deactivate a plugin:
POST /plugins
{ "plugin": "wordfence/wordfence.php", "action": "deactivate" }

Themes

GET  /themes
POST /themes
// Switch active theme:
POST /themes
{ "stylesheet": "eightball-genesis-child" }

Files

All file operations are restricted to /wp-content/. Paths outside this boundary return 403 Forbidden.

GET    /files?path=themes/my-theme/
GET    /files/read?path=themes/my-theme/single-service.php
POST   /files
DELETE /files?path=themes/my-theme/old-file.php
POST   /files/stage
POST   /files/commit

Write (create or overwrite) a file - plain content:

POST /files
{
  "path":    "themes/my-theme/single-service.php",
  "content": "<?php\n// file content here"
}

Write with base64-encoded content (use when a WAF blocks PHP code in POST bodies):

POST /files
{
  "path":        "themes/my-theme/single-service.php",
  "content_b64": "PD9waHAKLy8gZmlsZSBjb250ZW50IGhlcmU="
}

WAF-bypass two-step write (/files/stage + /files/commit)

Use this when a firewall (e.g. Cloudflare managed rules) blocks any POST body containing PHP code patterns. The content is sent as base64 in one or more small chunks, stored as transients, then written to disk by a separate commit call that carries no file content at all.

Step 1 - stage (repeat for each chunk):

POST /files/stage
{
  "path":        "themes/my-theme/single-service.php",
  "content_b64": "<base64-encoded chunk>",
  "chunk_index": 0,
  "chunk_total": 1
}

chunk_index is zero-based. For a single file, use chunk_index: 0, chunk_total: 1. Split large files into multiple chunks (max 200) and POST each one.

Step 2 - commit:

POST /files/commit
{
  "path":        "themes/my-theme/single-service.php",
  "chunk_total": 1
}

Staged chunks expire automatically after 1 hour if commit is never called.


Access Log

GET  /logs
POST /logs/clear
GET /logs?limit=100

Returns the most recent API requests (default 50, max 500). Each entry includes timestamp (UTC), client IP, HTTP method, endpoint, and response status.

POST /logs/clear
{}

Clears all log entries. Equivalent to the "Clear Log" button in WP Admin → Settings → Claude Connector.

Logging can be enabled or disabled from the plugin's settings page. The Last Access row on the settings page always shows the most recent request regardless of whether full logging is enabled.


Database

GET  /db/tables
POST /db/query
// SELECT query:
POST /db/query
{
  "query": "SELECT ID, post_title FROM wp_posts WHERE post_type = 'service' AND post_status = 'publish'",
  "type":  "get_results"
}

// Single value:
POST /db/query
{ "query": "SELECT COUNT(*) FROM wp_posts WHERE post_status = 'publish'", "type": "get_var" }

// Execute (UPDATE/DELETE/INSERT - use carefully):
POST /db/query
{ "query": "UPDATE wp_options SET option_value = '1' WHERE option_name = 'my_option'", "type": "query" }

type must be one of: get_results, get_row, get_var, get_col, query.


Security

How the key is protected

  • The API key is a 256-bit (64 hex char) random value generated on first activation.
  • Authentication uses hash_equals() for constant-time comparison, preventing timing attacks.
  • Keys passed as the X-Claude-Key header are not logged by default web servers.

What to avoid

  • The API key is only accepted via the X-Claude-Key header - there is no URL parameter fallback, so it can never end up in server access logs, browser history, or Referer headers.
  • Don't commit the key to version control. If you pin it via wp-config.php, make sure wp-config.php is in .gitignore.

File access boundary

The /files endpoints enforce a hard boundary at WP_CONTENT_DIR. Path traversal attempts (e.g. ../../wp-config.php) are blocked - realpath() is used to resolve symlinks and relative segments before the boundary check.

Protected options

The following options cannot be read or modified via /options (both GET and POST), to prevent accidental site breakage and to keep secrets out of API responses:

siteurl, home, admin_email, blogname, blogdescription, users_can_register, default_role, active_plugins, template, stylesheet, WordPress auth/salt keys, and the connector's own API key.

Note that this is a fixed blocklist, not a general secrets scanner - it won't catch, say, another plugin's API key stored inside a serialized option value under an unrelated option name. The /db/query and /options endpoints assume anyone holding the API key is fully trusted; see "Security" above.

Regenerating the key

Go to Settings → Claude Connector → Regenerate Key at any time. The old key stops working immediately.


How Claude uses this plugin

Once connected, Claude can handle tasks like:

"Sync the ACF field groups on example.com"
→ POST /acf/sync

"Flush the cache after those changes"
→ POST /cache/purge

"Show me all published service pages"
→ GET /posts?type=service&status=publish

"Read the current single-service.php"
→ GET /files/read?path=themes/eightball-genesis-child/single-service.php

"Update the hero heading on post ID 214"
→ PUT /posts/214

"What's in the wp_options table for the SEO plugin?"
→ POST /db/query

No SFTP. No SSH. No cPanel. No asking the client to do anything except install and activate the plugin once.


Requirements

  • WordPress 5.8+
  • PHP 7.4+ (tested on 7.4, 8.0, 8.1, 8.2, 8.3)
  • HTTPS strongly recommended (ensures the API key is encrypted in transit)

Changelog

1.6.0 — Divi 5 hardening

Fixes a data-loss bug and closes the verification loop. Derived from building a 44-page Divi 5 site end to end through the connector.

Fixed

  • Builder content is no longer destroyed on write. None of the three transports called wp_set_current_user(), so every request ran as user 0. WordPress attaches the kses filters whenever the current user lacks unfiltered_html, so wp_insert_post()/wp_update_post() HTML-escaped block delimiters — `became<!-- wp:divi/section --> — which silently made the layout unparseable while returning HTTP 200. The connector now assumes a real user (configurable, defaulting to the lowest-numbered administrator) at the single auth choke point, and explicitly drops the kses filters on multisite where administrators don't hold unfiltered_html. This also fixespost_author` defaulting to 0.
  • Every content write is verified. Block markup is re-read after writing and the request fails loudly if the delimiters were escaped, so this can never regress silently again.
  • POST /divi/data/{id} now applies the full Divi 5 postmeta set. It set only _et_pb_use_builder, which is enough for Divi 4 but leaves a Divi 5 page rendering on the theme's default template with a widget sidebar and a duplicated title. wp_posts_create/wp_posts_update infer the same when they detect `