Claude Code WordPress PHP CLAUDE.md Abilities API MCP AI Coding 2026

Claude Code for WordPress: CLAUDE.md Rules for the Abilities API, Block Themes, and Plugin Security (2026)

The Prompt Shelf ·

WordPress still runs over 40% of the web, which means it’s also one of the most inconsistently represented stacks in Claude Code’s training data — decades of tutorials mixing PHP 5-era globals-and-$_POST patterns with 2020s block editor conventions, classic themes with header.php/footer.php sitting next to block themes built entirely from theme.json, and admin-ajax.php snippets outnumbering the REST API and the brand-new Abilities API combined. Without a project-specific CLAUDE.md, Claude Code has no way to know which era your codebase belongs to, and WordPress’s specific failure mode is that the wrong era usually still runs — it just runs insecurely.

The gallery’s existing WordPress + Gutenberg Blocks rule set is a solid starting point for a Guzzle-backed REST API plugin, but it’s a short .cursorrules snippet: five bullet points on coding standards and TypeScript preference, nothing on block themes, nothing on the security rules that actually prevent the plugin from being a liability, and nothing on the Abilities API, which didn’t exist when it was written. This guide is meant to sit underneath it — a fuller CLAUDE.md that covers what a 2026 WordPress project actually needs pinned down.

Browse more real-world CLAUDE.md and AGENTS.md examples in our gallery.

Why a Generic “WordPress Best Practices” List Isn’t Enough

WordPress’s PHP compiles and its hooks fire regardless of whether the underlying code is safe, which means none of the following failure modes throw an error — they just ship:

  • Echoing $_POST/$_GET values directly into HTML instead of running them through esc_html()/esc_attr(), because the training data has thirteen years of tutorials that skip escaping for brevity
  • Building a raw SQL string with string concatenation instead of $wpdb->prepare(), especially inside a loop where the injection risk isn’t obvious from a quick read
  • Adding an admin action or REST route with no capability check, so any logged-in subscriber can trigger it
  • Skipping the nonce on a form or AJAX call, or checking it with wp_verify_nonce() but never actually calling check_admin_referer()/check_ajax_referer() at the top of the handler
  • Scaffolding a classic theme (header.php, footer.php, sidebar.php, functions.php full of add_action calls) on a project that’s actually a block theme built from theme.json and HTML template parts — the two structures don’t mix cleanly, and Claude Code has no way to know which one the project uses without being told
  • Reaching for admin-ajax.php for a new endpoint on a project that has standardized on the REST API (or the newer Abilities API), because admin-ajax.php shows up far more often in older training data
  • Registering hooks, options, and post meta keys without a unique prefix, which works fine until the plugin is active alongside another one that picked the same generic name

None of this fails php -l or a WordPress.org plugin upload. It just becomes the kind of vulnerability report that shows up in a security audit six months later.

What’s New in WordPress That Belongs in CLAUDE.md

WordPress moved fast enough in the last two releases that a CLAUDE.md written even a year ago is missing the thing most worth pinning down:

  • The Abilities API shipped in WordPress 6.9 (December 2025). It lets a plugin or theme register a typed, discoverable capability — wp_register_ability() with a name, an input/output schema, a permission_callback, and an execute_callback — that’s callable from PHP, JavaScript, and the REST API. It’s a structured alternative to hand-rolling a REST endpoint or an admin-ajax.php handler for every action.
  • The MCP Adapter turns registered abilities into tools an AI agent can call. It adapts whatever’s registered through the Abilities API into Model Context Protocol primitives, so a connected client — Claude Desktop, Claude Code, Cursor — can discover and execute site functionality directly. As of WordPress 7.0 (May 2026), the Adapter is not bundled in core; it’s still a separately maintained plugin on its own release cadence, so CLAUDE.md should say explicitly whether the project has it installed rather than assuming it’s active just because the site runs 7.0+.
  • WordPress 7.0 raised the practical PHP floor. Minimum supported PHP moved to 7.4, and the WordPress.org-recommended version is PHP 8.3. If the project targets 8.3, Claude Code can safely use readonly properties, enums, and named arguments; if it still supports hosts on 7.4 for compatibility, none of that is safe to assume.
  • Plugin Check (the official Plugin Check plugin) is now the standard local linter for anything headed to WordPress.org — it runs PHPCS with the WordPress ruleset plus additional security and performance sniffs, and it’s worth wiring into a PostToolUse hook the same way phpcs alone would be.

If the project hasn’t adopted the Abilities API or the MCP Adapter yet, that’s a normal thing to say plainly in CLAUDE.md — the failure mode is Claude Code assuming a newer capability exists because the WordPress version supports it, not because the project actually uses it.

A CLAUDE.md Template for WordPress Plugin/Theme Projects

# CLAUDE.md — WordPress Project

## Stack
- WordPress {version}, PHP {7.4 minimum / 8.3 target}
- Project type: {plugin / classic theme / block theme / mu-plugin}
- Prefix: `{myplugin}_` for functions, `{myplugin}-` for hooks/handles, `{MYPLUGIN_}` for constants — never a bare generic name
- Text domain: `{my-plugin}` — every user-facing string must be wrapped for translation
- Data layer: {REST API / Abilities API (if MCP Adapter installed) / admin-ajax.php (legacy, do not add new endpoints here)}

## Security — Non-Negotiable
- Every form and AJAX/REST handler that mutates data verifies a nonce first (`check_admin_referer()` / `check_ajax_referer()` / REST `permission_callback`). No exceptions for "internal" admin pages.
- Every privileged action checks `current_user_can()` before running — a valid nonce is not a substitute for a capability check.
- All database queries with dynamic values go through `$wpdb->prepare()`. Never build SQL with string concatenation or interpolation.
- All output is escaped at the point of output — `esc_html()`, `esc_attr()`, `esc_url()`, `wp_kses_post()` — not at the point of input. Sanitize on the way in, escape on the way out; do not treat these as the same step.
- Secrets (API keys, tokens) go in `wp-config.php` constants or an environment variable, never in `wp_options` in plaintext, never committed.

## Theme Structure (block theme projects)
- This is a block theme: templates live in `templates/*.html`, reusable pieces in `parts/*.html`, defaults in `theme.json`. Do NOT create `header.php`, `footer.php`, `sidebar.php`, or a `functions.php` full of PHP-templated markup — that's classic theme structure and the two do not mix.
- Global styles, spacing, and color palettes are defined in `theme.json`, not hardcoded CSS values scattered across block templates.
- New block patterns go in `patterns/*.php` with a `Registered Pattern` doc-block header, not inlined into a template.

## Abilities API (if MCP Adapter is installed — confirm before assuming)
- New site capabilities meant to be callable by an AI agent are registered with `wp_register_ability()`, not a bare REST route or an `admin-ajax.php` handler.
- Every ability declares an explicit `permission_callback` — an ability with no permission check is discoverable and executable by any connected MCP client, which is a materially larger blast radius than an unprotected REST route.
- Input/output schemas are required, not optional — an ability without a typed schema is what makes it useful to an agent in the first place.

## WP-CLI / Commands
- Scaffold: `wp scaffold plugin {slug}` / `wp scaffold block {namespace}/{name}`
- Lint: `composer run lint` (PHPCS, WordPress ruleset) — do not skip this before considering a task done
- Local env: `wp-env start` / `wp-env run cli wp {command}`
- Never run `wp db reset`, `wp site empty`, or `wp plugin delete` against anything but the `wp-env` container — these are destructive and irreversible against a real database.

The security section is deliberately not optional or phrased as a suggestion — every WordPress vulnerability disclosure that goes out through Patchstack or WPScan traces back to one of those five rules being skipped once, and Claude Code has no built-in reason to prioritize them over just making the feature work.

Block Themes vs. Classic Themes: Say Which One, Every Time

A CLAUDE.md that doesn’t specify the theme type is the single most common cause of Claude Code generating a file that doesn’t belong in the project at all:

Classic ThemeBlock Theme
TemplatesPHP files (page.php, single.php, header.php) with get_header()/get_footer() callsHTML files in templates/ using block markup, no PHP template tags
Styling source of truthstyle.css + enqueued stylesheetstheme.json (colors, spacing, typography presets)
Customization UICustomizer (add_theme_support, customize_register)Site Editor (full site editing)
Reusable regionssidebar.php, get_template_part()parts/*.html (header, footer)
functions.php roleOften large — hooks, widget areas, custom fields, template logicMinimal — mostly theme.json support flags and pattern/block registration

Mixing the two isn’t a hard error — WordPress will still render a header.php on a block theme if you add one — but it produces a theme that works inconsistently in the Site Editor and confuses the next person (or agent) who opens the codebase expecting one convention and finding both.

settings.json: Pre-Approving WP-CLI, Denying the Destructive Commands

WP-CLI runs constantly during a WordPress session — scaffolding, cache flushing, running the linter — and prompting for each invocation adds friction without adding safety, provided the genuinely destructive commands are denied explicitly:

{
  "permissions": {
    "allow": [
      "Bash(wp scaffold:*)",
      "Bash(wp-env run cli wp:*)",
      "Bash(wp-env start)",
      "Bash(composer run lint)",
      "Bash(vendor/bin/phpcs:*)",
      "Bash(git status)",
      "Bash(git diff:*)"
    ],
    "deny": [
      "Bash(wp db reset*)",
      "Bash(wp db drop*)",
      "Bash(wp site empty*)",
      "Bash(wp plugin delete*)"
    ]
  }
}

The deny list targets commands that are irreversible against a real database, not WP-CLI as a whole — the goal is letting Claude Code use the CLI freely for the 95% of commands that are safe to retry, while stopping to ask before the 5% that aren’t.

Pair the allowlist with a PostToolUse hook that runs PHPCS with the WordPress ruleset automatically after any .php edit, so coding-standard drift gets caught before it reaches a diff:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "if [[ \"$CLAUDE_FILE_PATH\" == *.php ]]; then vendor/bin/phpcs --standard=WordPress \"$CLAUDE_FILE_PATH\" 2>/dev/null; fi"
          }
        ]
      }
    ]
  }
}

AGENTS.md for Plugin + Theme Monorepos

Agencies and product companies that maintain a plugin and its companion theme (or several client sites) in one repository run into the same boundary problem Nx monorepos have in the JavaScript world — a flat CLAUDE.md bleeds plugin-specific conventions into theme code that has to work with other plugins active, and vice versa:

# AGENTS.md

## Global Rules
- Follow all rules in CLAUDE.md.
- Run `composer run lint` on any touched `.php` file before marking a task complete.
- Never edit generated files under `vendor/` or `node_modules/`.

## /plugin/
Agent scope: the plugin itself. Must function correctly when active on any theme, not just the companion theme in this repo.
- No dependency on theme-defined functions, constants, or template parts.
- All admin UI goes through the plugin's own settings page or block editor sidebar panel — never assumes theme customizer sections exist.

## /theme/
Agent scope: the block theme. Must render correctly with only default WordPress plugins active.
- No dependency on the companion plugin being active — feature-detect with `function_exists()` before calling any plugin function.
- Style values come from `theme.json`, not hardcoded in template parts.

## /theme/patterns/
Agent scope: reusable block patterns.
- Every pattern must render with placeholder content out of the box — no dependency on production data existing.

The “no dependency on the companion plugin/theme” rules matter because a plugin that quietly assumes its own theme is active will pass every test that runs in the monorepo’s CI and then break the moment a client switches themes — which is a support ticket, not a build failure Claude Code would ever see.

Common Mistakes to Watch For

Skipping the nonce check because the form only appears in wp-admin. “It’s behind the admin login” isn’t a substitute for CSRF protection — a logged-in admin’s browser can still submit a forged request from a malicious page in another tab, which is exactly what nonces exist to prevent.

Sanitizing on input instead of escaping on output (or doing both and calling it redundant). Sanitize-on-input strips or normalizes data before it’s stored; escape-on-output makes it safe for the specific context it’s rendered into (HTML, an attribute, a URL, JSON). Skipping the output escaping because the input was already sanitized is a common gap — sanitization rules and escaping rules solve different problems and both are required.

Adding header.php to a block theme “just to get something working quickly.” It will render, which is exactly why this mistake survives code review — but the Site Editor won’t manage it as a template, and the next person editing the site through the block editor won’t find it there.

Registering a REST route for something that’s meant to be agent-callable, instead of an Ability. A REST route works fine for a browser-based admin UI, but if the MCP Adapter is installed and the intent is for Claude Code (or another connected agent) to call the capability directly, it needs to go through wp_register_ability() — otherwise it’s invisible to MCP discovery entirely.

Assuming the MCP Adapter is active because the site runs WordPress 7.0+. The Abilities API is core as of 6.9; the MCP Adapter that exposes those abilities to AI clients is a separate plugin with its own release cadence. CLAUDE.md should state plainly whether it’s installed rather than leaving Claude Code to guess from the WordPress version alone.


WordPress’s biggest risk with Claude Code isn’t unfamiliarity — it’s the opposite. There’s so much WordPress code in the training data, spanning fifteen-plus years of conventions, that the agent will confidently produce something that runs. Pinning down the theme type, the security non-negotiables, and whether the Abilities API and MCP Adapter are actually part of the stack is what turns “runs” into “runs safely, in a structure the next person can actually maintain.”

Browse more real WordPress, PHP, and framework-specific CLAUDE.md/AGENTS.md examples in our gallery.


FAQ

Does Claude Code work well with WordPress? Yes for generating working code — WordPress’s hooks and template system compile and run regardless of whether security and structural conventions are followed, which is the actual risk. Claude Code has no way to know whether a project is a classic theme or a block theme, or whether nonces and capability checks are required project conventions, without a CLAUDE.md that says so explicitly.

What is the WordPress Abilities API? It’s a standardized way, introduced in WordPress 6.9 (December 2025), to register a typed, discoverable capability — via wp_register_ability() with an input/output schema and a permission callback — that’s callable from PHP, JavaScript, and the REST API. It’s the foundation the MCP Adapter uses to expose site functionality to AI agents like Claude Code.

Do I need the MCP Adapter to use Claude Code with WordPress? No. Claude Code works with WordPress code the same way it works with any PHP project — reading and editing files directly. The MCP Adapter is a separate, optional plugin that lets a connected AI client discover and execute registered Abilities directly against a live site (for example, “get the site info from my WordPress site”), which is a different use case from editing the codebase.

Should CLAUDE.md specify block theme or classic theme? Yes, explicitly. The two use incompatible template structures — PHP template files with get_header()/get_footer() calls for classic themes, versus HTML template parts and theme.json for block themes. Without this stated, Claude Code may scaffold a header.php on a block theme project, which renders but isn’t managed by the Site Editor the way the rest of the theme is.

Why are nonce checks and capability checks both required — isn’t one enough? They protect against different threats. A nonce confirms the request came from a legitimate form on your site (CSRF protection); a capability check confirms the user making the request is actually allowed to perform the action. A valid nonce says nothing about whether the user has permission, and a capability check alone doesn’t stop a forged request from a logged-in user’s own browser.

How is this different from the gallery’s WordPress + Gutenberg rule set? The gallery’s WordPress + Gutenberg rules are a short, plugin-focused .cursorrules snippet covering Guzzle HTTP and REST endpoints — useful as a starting point, but written before block themes were the default and before the Abilities API existed. This guide is meant to sit on top of it for a fuller project, covering theme structure, the security rules that aren’t optional, and the current AI-agent integration path.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules