DEV Community

Cover image for Four Architectures for Letting Claude Edit Elementor (and Why We Shipped Clone-and-Mutate)
Royal Plugins
Royal Plugins

Posted on • Originally published at royalplugins.com

Four Architectures for Letting Claude Edit Elementor (and Why We Shipped Clone-and-Mutate)

If you build with Elementor and you've been watching the MCP ecosystem evolve, you've probably had the same question we did six months ago: what does a working AI-to-Elementor pipeline actually look like? The YouTube demos show prompts becoming pages in twelve seconds. The Reddit threads show people frustrated that "AI page generation is broken." Both are partially true. The honest version is that there are four distinct architectures for letting Claude (or any LLM with tool use) edit Elementor pages today, and each one has a clear shape, a clear failure mode, and a clear use case.

This is the engineer's version of our user-facing guide. We'll cover the four patterns, then deep-dive on the one we shipped in Royal MCP 1.4.19 — including the actual tool signatures, the data model under the hood, and the auth tradeoffs.


The four architectures, briefly

# Architecture What Claude actually does Surface area
1 Write-through to Gutenberg Calls POST /wp/v2/posts with Gutenberg block markup; Elementor wraps each block as a widget on read Tiny (WP core REST)
2 Layout-decoupled Drafts copy in chat; human pastes into Elementor widgets manually None (Claude never touches WP)
3 Clone-and-mutate Reads an existing Elementor page, deep-clones the serialized structure, replaces text/URLs in the copy, saves as draft Narrow (6 tools, no schema decoding)
4 Synthesize-from-prompt Reads the brief, emits a fresh Elementor JSON tree from scratch, writes it to _elementor_data Huge (must decode every widget schema)

Pattern 4 is what people picture when they say "AI builds Elementor pages." Pattern 3 is what we shipped. The rest of this post explains why.


Pattern 1 — Write-through to Gutenberg

The shape. Claude writes block markup to wp_posts.post_content via the standard WordPress REST API. Elementor's "Edit with Elementor" view auto-wraps each Gutenberg block as a widget on first open.

The minimal tool call (what an MCP server like ours emits when Claude says "publish this post"):

{
  "method": "tools/call",
  "params": {
    "name": "wp_create_post",
    "arguments": {
      "title": "How We Cut Cold-Start Latency by 40%",
      "status": "draft",
      "content": "<!-- wp:paragraph -->\n<p>Our P99 was sitting at 3.2s...</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">The bottleneck</h2>\n<!-- /wp:heading -->"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Where this falls apart. It only works for posts where Gutenberg block markup is the source of truth. If a page was built widget-by-widget in the Elementor editor (which is most marketing pages), wp_posts.post_content is empty — the structure lives in wp_postmeta under _elementor_data as serialized JSON. Claude has nothing to write to.

Detection is one query:

SELECT
  p.ID,
  CASE
    WHEN m.meta_value IS NOT NULL AND LENGTH(p.post_content) < 50 THEN 'elementor-native'
    WHEN m.meta_value IS NOT NULL                                  THEN 'hybrid'
    ELSE                                                                'gutenberg'
  END AS edit_path
FROM wp_posts p
LEFT JOIN wp_postmeta m
  ON m.post_id = p.ID AND m.meta_key = '_elementor_data'
WHERE p.post_type = 'page' AND p.post_status = 'publish';
Enter fullscreen mode Exit fullscreen mode

If the row comes back elementor-native, Pattern 1 is a dead end for that page and you need Pattern 3 or 4.

Best for: blog posts, long-form content, anything where the layout is template-driven and AI's job is to write words, not arrange pixels.


Pattern 2 — Layout-decoupled

The shape. Claude never touches WordPress. You ask it for headlines, body copy, FAQ answers, CTA strings. You paste into Elementor widgets manually.

There's no code to show here because there's nothing under the hood — it's a workflow, not a system. We include it because for tentpole marketing pages where every pixel matters, it's still the right answer. Human-grade layout judgement plus AI-grade copy drafting is a sane division of labor.

Best for: new page designs without a precedent, A/B variants where you want fine control, anything with custom CSS or scroll-triggered animations.


Pattern 3 — Clone-and-mutate (the one we shipped)

This is the architecture we built Royal MCP around. The premise is simple: never let the LLM author Elementor JSON from scratch. Instead, treat an existing working page as a template, deep-clone it, and mutate string values inside the cloned tree.

The six tools

Royal MCP 1.4.19 exposes six Elementor tools through its MCP endpoint. Their signatures, abbreviated:

// Read the structural outline (no widget internals)
elementor_get_page_outline(post_id: number): {
  post_id: number;
  title: string;
  sections: Array<{
    id: string;
    type: 'section' | 'container';
    children_count: number;
    has_text_widgets: boolean;
    has_image_widgets: boolean;
  }>;
}

// Deep-clone a published page → new draft with fresh widget IDs
elementor_clone_page(
  source_post_id: number,
  new_title: string,
  new_status?: 'draft' | 'private'   // default: draft
): { new_post_id: number; new_slug: string; edit_url: string; }

// Replace literal strings in editable widget fields, tree-wide
elementor_replace_text(
  post_id: number,
  find: string,
  replace: string,
  case_sensitive?: boolean   // default: false
): { replacements: number; widgets_touched: string[]; }

// Swap media URLs in image/background widgets
elementor_replace_image(
  post_id: number,
  old_url: string,
  new_url: string
): { replacements: number; }

// List saved Elementor templates available locally
elementor_list_local_templates(): Array<{
  template_id: number;
  title: string;
  type: 'page' | 'section' | 'container';
}>;

// Import a local template into a new draft page
elementor_import_template(
  template_id: number,
  new_title: string
): { new_post_id: number; }
Enter fullscreen mode Exit fullscreen mode

Two things to notice: there's no elementor_create_widget, and there's no elementor_set_widget_property. Both would require the MCP server to understand the widget schema. We deliberately don't.

The algorithm

elementor_clone_page is roughly 40 lines of PHP. The interesting part:

function rmcp_elementor_clone_page( $source_post_id, $new_title, $new_status = 'draft' ) {
    // 1. Pull the serialized Elementor tree from postmeta.
    $data = get_post_meta( $source_post_id, '_elementor_data', true );
    if ( empty( $data ) ) {
        return new WP_Error( 'rmcp_not_elementor', 'Source page is not Elementor-native.' );
    }

    $tree = json_decode( $data, true );
    if ( ! is_array( $tree ) ) {
        return new WP_Error( 'rmcp_invalid_data', 'Could not decode _elementor_data.' );
    }

    // 2. Walk the tree and regenerate every element ID. Elementor uses these
    //    as DOM anchors; reusing them would cause the source + clone to share
    //    edit-handle state in the editor (same widget appears in two posts).
    $tree = rmcp_regenerate_element_ids( $tree );

    // 3. Insert as a new draft. Empty post_content is fine — Elementor reads
    //    everything from _elementor_data on render.
    $new_post_id = wp_insert_post( [
        'post_title'   => $new_title,
        'post_status'  => $new_status,
        'post_type'    => get_post_type( $source_post_id ),
        'post_content' => '',
    ], true );

    if ( is_wp_error( $new_post_id ) ) {
        return $new_post_id;
    }

    // 4. Write the cloned tree + the marker meta Elementor uses to detect
    //    "this page is built with Elementor" on load.
    update_post_meta( $new_post_id, '_elementor_data', wp_slash( wp_json_encode( $tree ) ) );
    update_post_meta( $new_post_id, '_elementor_edit_mode', 'builder' );
    update_post_meta( $new_post_id, '_elementor_template_type', 'wp-page' );
    update_post_meta( $new_post_id, '_elementor_version', get_post_meta( $source_post_id, '_elementor_version', true ) );

    return [
        'new_post_id' => $new_post_id,
        'new_slug'    => get_post_field( 'post_name', $new_post_id ),
        'edit_url'    => admin_url( 'post.php?post=' . $new_post_id . '&action=elementor' ),
    ];
}

function rmcp_regenerate_element_ids( $tree ) {
    foreach ( $tree as &$element ) {
        if ( isset( $element['id'] ) ) {
            $element['id'] = substr( md5( uniqid( '', true ) ), 0, 7 );
        }
        if ( ! empty( $element['elements'] ) && is_array( $element['elements'] ) ) {
            $element['elements'] = rmcp_regenerate_element_ids( $element['elements'] );
        }
    }
    return $tree;
}
Enter fullscreen mode Exit fullscreen mode

elementor_replace_text follows the same shape but instead of regenerating IDs, it walks the tree looking for a small known set of editable-text fields per widget type (title, text, editor, button_text, heading_text, caption, etc.), runs an str_ireplace on each matched value, and returns a count.

The whole MCP server is ~600 lines of PHP per Elementor tool, and most of that is input validation + capability checks. The reason is exactly what we don't do: we don't model widgets, we don't decode widget settings, we don't have a schema registry that needs to keep up with Elementor's releases.

Why this survives Elementor's roadmap

Elementor has been actively reshaping how widgets work — Container model (v3.20), atomic elements (v4.0+), the Editor V4 rebuild. Any synthesize-from-prompt MCP (Pattern 4) has to maintain a model of these widgets to emit valid JSON for them. Clone-and-mutate doesn't decode the schema at all. Atomic widgets pass through opaque. Pro widgets pass through opaque. Custom third-party widgets pass through opaque. As long as the source page renders, the cloned variant renders.

The contract is: "copy whatever's there, change the strings I asked you to change, leave widget internals untouched."

Failure modes are loud, not silent

The biggest argument for this pattern in production: there's no "page generated but it's broken" mystery. Each operation has a binary outcome with a count attached:

  • elementor_clone_page returns a new_post_id or an error.
  • elementor_replace_text returns {replacements: 0} or {replacements: N, widgets_touched: [...]}. If replacements: 0, the operation didn't silently corrupt anything — it just didn't find what to replace, and Claude knows.

Compare that to a synthesize-from-prompt MCP that emits invalid Elementor JSON for an atomic widget the LLM hasn't seen before: the page exists in the database but won't render, or worse, renders with a fatal error in the Elementor editor preview.


Pattern 4 — Synthesize-from-prompt

This is the architecturally ambitious option. Two projects worth watching:

Angiewp.org/plugins/angie. Elementor's own first-party MCP plugin. Currently sitting at a 2.4-star average on wp.org. The headline framing is correct (first-party MCP from the vendor) but the early-days reliability concerns are real. Worth checking back on as the team iterates; not yet production-ready by user feedback.

msrbuilds/elementor-mcpgithub.com/msrbuilds/elementor-mcp. The leading community implementation. ~316 stars, 79 forks, active maintenance, 110 tools spanning query, page management, layout, widgets, templates, theme builder, global settings, and stock images. Recently added atomic widget support (April 2026). It's the most ambitious build-from-scratch toolset we've seen.

Auth model is the headline tradeoff

This is the part most articles skip. The auth model determines how big a problem a leaked credential is.

# Pattern 4 (msrbuilds-style): WordPress Application Password over HTTP Basic
GET /wp-json/wp/v2/elementor/pages/42 HTTP/1.1
Host: example.com
Authorization: Basic d3BfdXNlcjphcHBfcGFzc3dvcmRfc3RyaW5n
Enter fullscreen mode Exit fullscreen mode
# Pattern 3 (Royal MCP): OAuth 2.1 bearer or scoped API key
POST /wp-json/royal-mcp/v1/mcp HTTP/1.1
Host: example.com
Authorization: Bearer rmcp_pat_a1b2c3d4...
Content-Type: application/json

{ "method": "tools/call", "params": { ... } }
Enter fullscreen mode Exit fullscreen mode

The difference matters because WordPress Application Passwords authenticate against the full WP REST API, not just the MCP route. A leaked Application Password gives an attacker every endpoint the user has permission to hit — Users, Posts, Media, plugin REST routes, the works. A scoped MCP API key (or an OAuth access token issued with royal-mcp:* scope) is narrowed to MCP calls only, with audit logging on each invocation.

This isn't a knock on msrbuilds specifically — HTTP Basic Auth with Application Passwords is the path of least resistance for a single-developer plugin and it's the default Claude Desktop documentation reaches for. It just deserves to be in the threat model for production sites.

Distribution matters too

msrbuilds is GitHub releases only — not in the wp.org plugin directory. That means:

  • No formal plugin review (wp.org runs a security pre-screen on submission and a code review on each release).
  • No auto-update through WP admin — you check the releases page yourself.
  • No surface for wp.org reviews when something breaks, so issue triage happens entirely in the GitHub issues tab.

Worth checking the issues tab before installing on a production site — most active issues are minor, but a couple recently touched atomic-widget edge cases that are still being smoothed out.


Decision matrix

Task Best pattern Why
Add a new blog post to a Gutenberg-native theme #1 (write-through) Layout is template-driven; AI writes words
One-off marketing page where pixels matter #2 (layout-decoupled) Human layout, AI copy
Spin up city/industry/audience variants of a working page #3 (clone-and-mutate) Existing design carries; only strings change
Bulk landing pages from a working template #3 (clone-and-mutate) Same as above, at volume
"AI built this page from a prompt" demo for stakeholders #4 (synthesize) This is what synthesize is for
Greenfield page design with no precedent #2 or #4 Either human-led layout or accept the schema risk

The pattern, generally: start with what already works unless you specifically need greenfield design. LLMs are at their best customizing something that exists, not inventing from blank canvas.


Connect Royal MCP to Claude in 60 seconds

If you want to try Pattern 3:

1. Install Royal MCP from the WordPress directory. Activate. Royal MCP → Settings → generate an API key. (Or skip the key and use OAuth 2.1 — both auth flows are supported.)

2. For Claude.ai web (no client needed):

In Claude → Settings → Connectors → Add Custom Connector → URL:

https://yoursite.com/wp-json/royal-mcp/v1/mcp
Enter fullscreen mode Exit fullscreen mode

Approve the OAuth consent screen. Done.

3. For Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "royal-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://yoursite.com/wp-json/royal-mcp/v1/mcp",
        "--header",
        "Authorization: Bearer rmcp_pat_YOUR_API_KEY"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Restart Claude Desktop. The six Elementor tools (plus 116 other WP + plugin tools) become available to Claude.

4. Verify the connection from curl:

curl -X POST https://yoursite.com/wp-json/royal-mcp/v1/mcp \
  -H "Authorization: Bearer rmcp_pat_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
  | jq '.result.tools[] | select(.name | startswith("elementor_")) | .name'
Enter fullscreen mode Exit fullscreen mode

Expected output:

"elementor_clone_page"
"elementor_get_page_outline"
"elementor_replace_text"
"elementor_replace_image"
"elementor_list_local_templates"
"elementor_import_template"
Enter fullscreen mode Exit fullscreen mode

Then in Claude:

"Clone my services-new-york page. Replace 'New York' with 'Los Angeles' everywhere. Change the hero background to the LA skyline photo I uploaded last week. Save as a draft."

Claude calls elementor_clone_page, then elementor_replace_text, then elementor_replace_image. You get a new draft page in under a minute. Container structure is preserved. Widget IDs are regenerated. The design system is intact because you never asked the LLM to invent it.


Why we shipped Pattern 3 and not Pattern 4

We considered building a synthesize-from-prompt MCP. The reasons we landed on clone-and-mutate instead, in priority order:

  1. It survives Elementor's roadmap. Container → atomic → Editor V4. We don't have to chase any of it. The synthesize MCPs do, and the maintenance cost is real.
  2. It doesn't fail dramatically. Binary outcomes with counts. No half-corrupted pages. No hallucinated widget types.
  3. It plays to the LLM's actual strengths. "Take this existing thing and produce a variant" is a problem class LLMs solve well today. "Author a coherent visual design from scratch" is one they don't.
  4. It composes with the design work you already paid for. You hired a designer or built the template yourself. Clone-and-mutate respects that work; synthesize starts over every time.
  5. Capability gating is per-post, not per-site. Every Royal MCP tool checks edit_posts and edit_post for the specific target. The plugin runs through wp.org's security review before each release. Auth is OAuth 2.1 or scoped key — not Base64'd credentials on every request.

Royal MCP 1.4.19 ships the six Elementor tools alongside the 116 existing WordPress + plugin tools. Free on the wp.org plugin directory. No upsell — pay for the design once, reuse it forever via AI.


What I'd actually use each for

If you're hacking on a personal site for fun — try Pattern 4. The demos are real, the failure modes are visible to you, and you'll learn a lot about how Elementor's data model actually works.

If you're shipping landing pages for clients or running your own marketing site — Pattern 3 is the architecture you want. The narrow tool surface, the schema-resilience, and the OAuth/scoped-key auth model are the production-readiness story.

If you're writing blog posts at scale — Pattern 1 plus a Gutenberg-native theme is still the lowest-friction path. Don't reach for an Elementor-specific MCP if you don't need Elementor for what you're publishing.


Disclaimer: I work on Royal MCP. I've tried to honestly reflect each project's current state. Tool counts, ratings, and feature data were gathered in May 2026 from each project's official listing. Project status changes — verify on the source before deciding.

If you want the user-facing version of this argument with concrete prompts and customer examples, the original post on royalplugins.com is the place. This is the engineer's cut.

Top comments (0)