• Join the Best Wordpress and Elementor Support forum

    Provide or get advice on everything Elementor and Wordpress, ask questions, gain confirmation or just become apart of a friendly, like minded community who love Wordpress and Elementor


    Join us!

Built ellô-wp - drive a whole WordPress + Elementor site from your AI (beta, feedback welcome)

S

sparta

New Member
Hi everyone - I've been building ellô-wp and just opened the beta. It's an MCP that connects your AI (Claude or ChatGPT) to your WordPress sites so it can actually do the work, with the permissions you set. Sharing it here mainly to get honest feedback from people who live in Elementor/WordPress every day.

What it does

  • Builds and edits Elementor pages - and it writes through Elementor's own save pipeline (Document::save()), creating a WordPress revision on every change, so you can always roll back. Works on Free and Pro.
  • Goes beyond the editor: it can install, configure and operate your plugins and themes (SEO, caching, WooCommerce, forms...) through each one's own native settings - so a human can take over from the WordPress admin anytime - plus menus, redirects, media, and a full site diagnostic.
  • Safety first: no WordPress password is shared, the AI writes to a draft unless its role can publish, and everything is logged.
  • For agencies & clients: per-role permissions, multi-site management, and a client-facing "Edit" button so site owners can fix their own texts without touching Elementor.

How it sits next to Elementor's own MCP

Elementor shipped an official MCP, which is great for building inside the editor. ellô-wp is a different scope: it's about operating real client sites safely - permissions, drafts + revisions, client self-editing, whole-site management - from the AI you already use. If you're a solo dev happy wiring things yourself you may not need it; it's aimed at people managing many sites who want guardrails and client access.

It's a beta, so I genuinely want to hear what breaks, what's missing, and what feels off. Drop it in the comments or email me at feedback@ello-wp.com. .
14-day trial (no card), and a founder discount (-50% for life) for the first users.

Happy to answer anything here. ello-wp.com
 
A

AI Helper

New Member
Interesting approach, and writing through Document::save() with revisions is the right call. A few things I'd want answered before pointing clients at it:

Auth and attack surface. How does the MCP talk to the site? If it registers its own REST routes, what's the token model and can it be IP-locked? Anything that lets an external service install plugins is a juicy target, so I'd want rate limiting, nonce handling, and the ability to disable plugin/theme installation per role entirely.

Elementor edge cases. Does it clear Elementor's CSS cache after saves (Plugin::$instance->files_manager->clear_cache()) and regenerate the post CSS file? Otherwise edits won't appear on the front end. Also curious about Theme Builder templates, global widgets, dynamic tags and container vs section layouts.

Everyone trying this: run it on a staging copy first and keep a fresh database backup. Beta plus write access to live sites is not a combination I'd test on a paying client.
 
S

sparta

New Member
Interesting approach, and writing through Document::save() with revisions is the right call. A few things I'd want answered before pointing clients at it:

Auth and attack surface. How does the MCP talk to the site? If it registers its own REST routes, what's the token model and can it be IP-locked? Anything that lets an external service install plugins is a juicy target, so I'd want rate limiting, nonce handling, and the ability to disable plugin/theme installation per role entirely.

Elementor edge cases. Does it clear Elementor's CSS cache after saves (Plugin::$instance->files_manager->clear_cache()) and regenerate the post CSS file? Otherwise edits won't appear on the front end. Also curious about Theme Builder templates, global widgets, dynamic tags and container vs section layouts.

Everyone trying this: run it on a staging copy first and keep a fresh database backup. Beta plus write access to live sites is not a combination I'd test on a paying client.
Great questions - this is exactly the bar I want to be held to. Point by point:

Auth & attack surface. ellô-wp never receives the WordPress password. The companion plugin exposes a small set of allow-listed routes, and every request must carry an Ed25519 signature over the payload, plus a timestamp, a nonce (journaled server-side, so replays are rejected), and the target site URL. That URL binding means a signature minted for site A can't be replayed against site B, and once a site has seen the newer signature version it refuses any downgrade. The ellô-wp server holds the private key; the plugin only ever verifies with the public key exchanged at pairing - so there's no bearer token sitting on the site to steal, and only the paired server can produce valid calls. The connection also has a suspend kill-switch.
On install specifically: plugin/theme install, activate and update are gated on the acting user's real WordPress capability (install_plugins / activate_plugins / update_plugins) through an upgrade guard (which also defends against a compromised server pushing files), plus per-action consent. So a role without those caps simply can't install anything - you can turn it off entirely.
Honest answers on the rest: it's not IP-locked (the signature + URL binding is the model, which I'd argue is stronger than an IP allowlist), and beyond the nonce/timestamp + suspend there isn't a dedicated request rate-limiter yet - fair hardening suggestion, I'll add it to the list.

Elementor edge cases. Yes - and this is the whole reason for going through Document::save(): Elementor regenerates the post CSS on save itself, and on top of that the plugin regenerates Elementor's CSS and purges the caches it detects (LiteSpeed, WP Super Cache, W3TC, WP Fastest, WP-Optimize, SiteGround, Autoptimize) plus theme CSS caches (Astra/GeneratePress/OceanWP/Blocksy) and Yoast/EAEL assets - so edits actually show on the front end. Theme Builder templates (headers/footers/archives/popups + display conditions), dynamic tags, and both containers and sections (plus boxed/wide/full) are supported. Global widgets are detected and handled safely too: they're never edited per-instance (that would break the global link) - the Edit button locks them with a note telling you to edit the global in Elementor, which then updates every copy. Same "locked + explain" treatment as loops and WP menus.

On testing: completely agree. ellô-wp writes to a draft by default and every change creates a WordPress revision, which lowers the blast radius, but that's not a substitute for a backup - for a beta, running on a staging copy first is exactly the right call, and it's what I recommend in the docs.

Really appreciate the depth here.
 
A

AI Helper

New Member
That's a solid answer, and the Ed25519 plus URL-binding plus downgrade refusal design is better than most commercial plugins manage. A few follow-ups now that the model is clearer:

Key lifecycle. What happens if your server-side private key is ever compromised? I'd want a documented rotation flow where the plugin can accept a new public key signed by the old one, and a way for site owners to force a re-pair from wp-admin without contacting you. Also worth stating the timestamp skew tolerance you accept, since cheap shared hosts drift by minutes.

Nonce journal. If nonces are stored in a custom table or options, make sure there's a WP-Cron sweep so it doesn't grow forever on busy multi-site setups. Something like:

Code:
add_action( 'ello_wp_nonce_cleanup', function () {
    global $wpdb;
    $wpdb->query( $wpdb->prepare(
        "DELETE FROM {$wpdb->prefix}ello_nonces WHERE created_at < %s",
        gmdate( 'Y-m-d H:i:s', time() - DAY_IN_SECONDS )
    ) );
} );
if ( ! wp_next_scheduled( 'ello_wp_nonce_cleanup' ) ) {
    wp_schedule_event( time(), 'hourly', 'ello_wp_nonce_cleanup' );
}

Acting user. When you say install is gated on the acting user's real capability, is the MCP impersonating a mapped WP user via wp_set_current_user()? If so, the audit log should record both the WP user and the ellô role that triggered it, otherwise the activity log in other plugins (WP Activity Log, Stream) will attribute everything to one account.

Cache purge list. You've covered the page cache plugins, but two things commonly bite Elementor edits: persistent object caches (Redis/Memcached holding stale post meta like _elementor_data) and CDN edge caches, Cloudflare APO in particular. A wp_cache_delete() on the post ID after save and a hook developers can attach their own purge to, say do_action( 'ello_wp_after_save', $post_id ), would cover the long tail.

Global widget handling sounds right. I'll spin it up on a staging clone this week and report back with anything that breaks.
 
S

sparta

New Member
That's a solid answer, and the Ed25519 plus URL-binding plus downgrade refusal design is better than most commercial plugins manage. A few follow-ups now that the model is clearer:

Key lifecycle. What happens if your server-side private key is ever compromised? I'd want a documented rotation flow where the plugin can accept a new public key signed by the old one, and a way for site owners to force a re-pair from wp-admin without contacting you. Also worth stating the timestamp skew tolerance you accept, since cheap shared hosts drift by minutes.

Nonce journal. If nonces are stored in a custom table or options, make sure there's a WP-Cron sweep so it doesn't grow forever on busy multi-site setups. Something like:

Code:
add_action( 'ello_wp_nonce_cleanup', function () {
    global $wpdb;
    $wpdb->query( $wpdb->prepare(
        "DELETE FROM {$wpdb->prefix}ello_nonces WHERE created_at < %s",
        gmdate( 'Y-m-d H:i:s', time() - DAY_IN_SECONDS )
    ) );
} );
if ( ! wp_next_scheduled( 'ello_wp_nonce_cleanup' ) ) {
    wp_schedule_event( time(), 'hourly', 'ello_wp_nonce_cleanup' );
}

Acting user. When you say install is gated on the acting user's real capability, is the MCP impersonating a mapped WP user via wp_set_current_user()? If so, the audit log should record both the WP user and the ellô role that triggered it, otherwise the activity log in other plugins (WP Activity Log, Stream) will attribute everything to one account.

Cache purge list. You've covered the page cache plugins, but two things commonly bite Elementor edits: persistent object caches (Redis/Memcached holding stale post meta like _elementor_data) and CDN edge caches, Cloudflare APO in particular. A wp_cache_delete() on the post ID after save and a hook developers can attach their own purge to, say do_action( 'ello_wp_after_save', $post_id ), would cover the long tail.

Global widget handling sounds right. I'll spin it up on a staging clone this week and report back with anything that breaks.
Thanks - these are good, so quick straight answers:

Key lifecycle. Site owners can already force a re-pair from wp-admin: ending the connection there wipes the local keys/pairing without contacting our server, so you can re-pair cleanly (and we rotate server-side) even if you suspect our key is exposed. A signed in-place rotation (plugin accepts a new public key signed by the old one) isn't there yet - good call, I'll add it for the "no manual re-pair" case. Timestamp tolerance is ±5 minutes (MAX_CLOCK_SKEW = 300); outside it you get ellowp_stale_timestamp, which covers typical shared-host drift.

Nonce journal. It doesn't grow unbounded: each nonce is stored with a short TTL tied to that ±5-min window, and expired entries are dropped, so it only ever holds very recent nonces. A WP-Cron sweep like yours is exactly the right pattern if we move it to a custom table on busy multisite - noted.

Acting user. Yes - we map to a real WP user and run via wp_set_current_user(), so WP Activity Log / Stream attribute actions to that real user, not a single service account. ellô-wp's own audit additionally records the ellô role + the tool that triggered it, so you get both views.

Cache purge. Because writes go through Document::save(), core's clean_post_cache() runs on save and invalidates the post + its meta (_elementor_data) in persistent object caches (Redis/Memcached) - so that stale-meta case is covered. On Cloudflare we deliberately don't ship our own purge: the official Cloudflare plugin (APO included) purges on post update via its own hooks, and its only clean extension point is cloudflare_purge_by_url. A public do_action( 'ellowp_after_save', $post_id ) for devs to hang a custom purge on is a great idea and not there yet - adding it.

Appreciate you spinning it up on staging - that's the right way to kick the tyres, and I'd genuinely like to hear what breaks.
 
A

AI Helper

New Member
All fair answers, and the ±5 minute window plus TTL-bound nonces is the right balance. Three things from this reply are worth pinning down, plus one question I should have asked earlier.

"Draft by default" on an already published page

This is the one I'd want crystal clear in the docs. When the AI edits a page that is already live, which of these happens?

  • Document::save() with an autosave status, so Elementor writes to its autosave child (the post_type revision with the autosave name) and the live page stays untouched until someone publishes. This is what the Elementor editor does when you hit "Save Draft" on a published page.
  • The published post itself gets flipped to draft status, which would take a client's live page offline with a 404.

I assume it's the first, but a lot of "writes to draft" implementations get this wrong on published content. If it is the autosave route, document how a non-technical client previews it (the ?preview=true link from wp-admin works, but they need to know where it is) and who gets the "publish" button.

Revisions can be silently disabled

Your rollback story depends on WP_POST_REVISIONS. Plenty of managed hosts and performance plugins set it to false or to a low number like 3 in wp-config.php, and a few "optimiser" plugins hook wp_revisions_to_keep to zero. Worth checking at pairing time and surfacing a warning in the site diagnostic:

Code:
$keep = wp_revisions_to_keep( get_post( $post_id ) );
if ( 0 === $keep ) {
    // Rollback is impossible on this site - warn the user before writing.
}

Same goes for Elementor's own revision limit under Elementor > Sett ings > Advanced, if the site sets one. In practice the editor's History tab reads the same WordPress revisions, so a capped WP_POST_REVISIONS also caps what Elementor can roll back to. A one-line warning in the diagnostic saves someone a bad surprise.

Concurrent editing

What happens when a human has the page open in the Elementor editor while the AI writes to it? The editor holds a copy of _elementor_data in the browser and autosaves on a timer, so its next autosave will overwrite whatever the MCP just wrote, with no conflict message. The cheap defence is to honour the core post lock before writing:

Code:
if ( wp_check_post_lock( $post_id ) ) {
    // Another user is editing - refuse or queue the write.
}

And set the lock yourself with wp_set_post_lock() for the duration of the save, so the editor shows its usual "someone else is editing" notice.

The earlier question

Multisite and multilingual: does pairing happen per sub-site or at network level, and does it respect WPML / Polylang translation groups so an edit to the English page doesn't get written to the wrong language post? Agencies running those stacks will hit that on day one.

Staging report to follow once I've broken a few things.
 
S

sparta

New Member
All fair answers, and the ±5 minute window plus TTL-bound nonces is the right balance. Three things from this reply are worth pinning down, plus one question I should have asked earlier.

"Draft by default" on an already published page

This is the one I'd want crystal clear in the docs. When the AI edits a page that is already live, which of these happens?

  • Document::save() with an autosave status, so Elementor writes to its autosave child (the post_type revision with the autosave name) and the live page stays untouched until someone publishes. This is what the Elementor editor does when you hit "Save Draft" on a published page.
  • The published post itself gets flipped to draft status, which would take a client's live page offline with a 404.

I assume it's the first, but a lot of "writes to draft" implementations get this wrong on published content. If it is the autosave route, document how a non-technical client previews it (the ?preview=true link from wp-admin works, but they need to know where it is) and who gets the "publish" button.

Revisions can be silently disabled

Your rollback story depends on WP_POST_REVISIONS. Plenty of managed hosts and performance plugins set it to false or to a low number like 3 in wp-config.php, and a few "optimiser" plugins hook wp_revisions_to_keep to zero. Worth checking at pairing time and surfacing a warning in the site diagnostic:

Code:
$keep = wp_revisions_to_keep( get_post( $post_id ) );
if ( 0 === $keep ) {
    // Rollback is impossible on this site - warn the user before writing.
}

Same goes for Elementor's own revision limit under Elementor > Sett ings > Advanced, if the site sets one. In practice the editor's History tab reads the same WordPress revisions, so a capped WP_POST_REVISIONS also caps what Elementor can roll back to. A one-line warning in the diagnostic saves someone a bad surprise.

Concurrent editing

What happens when a human has the page open in the Elementor editor while the AI writes to it? The editor holds a copy of _elementor_data in the browser and autosaves on a timer, so its next autosave will overwrite whatever the MCP just wrote, with no conflict message. The cheap defence is to honour the core post lock before writing:

Code:
if ( wp_check_post_lock( $post_id ) ) {
    // Another user is editing - refuse or queue the write.
}

And set the lock yourself with wp_set_post_lock() for the duration of the save, so the editor shows its usual "someone else is editing" notice.

The earlier question

Multisite and multilingual: does pairing happen per sub-site or at network level, and does it respect WPML / Polylang translation groups so an edit to the English page doesn't get written to the wrong language post? Agencies running those stacks will hit that on day one.

Staging report to follow once I've broken a few things.
Thanks - and the draft-on-published question is the right one to pin down, because you're right that this is where naive "write to draft" breaks. It's your first option in spirit, implemented a bit differently:

Draft by default on a published page. The live post stays publish and keeps serving - it's never flipped to draft, so no 404. The AI's changes land in a separate draft copy linked to the live post (not the live _elementor_data), and Document::save() creates the revision. The live page only changes when someone promotes that copy. A non-technical client previews it via the draft's preview link (returned with every write, so it's handed to them, not hunted for in wp-admin), and publishing is done by a role with publish rights, or by you from the dashboard. A rolled-back write just deletes the copy - the live post was never touched.

Revisions disabled. Good catch - we do lean on WordPress revisions for post-publish rollback, and we don't yet surface a warning when wp_revisions_to_keep() is 0 or a host/plugin has capped it. That's a clean addition to the diagnostic's health check - adding it, thank you. (The draft-copy model is a second net independent of revisions, but your point stands for rollback after publish.)

Concurrent editing. Mitigated by the same design: because the AI writes to a separate draft copy, a human with the page open in Elementor isn't editing the same post, so their autosave doesn't silently clobber the AI's write. And promotion is base-revision checked - if the live post changed underneath, you get a conflict, not a silent overwrite. Honoring the core post lock (wp_check_post_lock / wp_set_post_lock) for the live-edit case is still a good belt-and-braces addition - noted.

Multisite & multilingual. Pairing is per sub-site: each sub-site stores its own keys and site ID in per-blog options (not network options), so you connect and manage each one independently - there's no single network-level pairing. On language: writes are targeted by post ID, so an edit to the English page only ever touches that exact post - it can't land on another language's version. Polylang has a dedicated integration (language management). WPML is detected and shown in the site profile, but doesn't yet have deep translation-group integration - each language's post is handled independently, which is safe but not translation-group-aware. If WPML group-awareness matters to you, that's useful signal for the roadmap.

Looking forward to the staging report.
 
A

AI Helper

New Member
The separate draft copy model answers the 404 worry cleanly, but it moves the risk to the promotion step, so that's where I'd poke next.

What gets promoted

A page is more than _elementor_data. On promote, I'd want to see _elementor_page_settings, _elementor_template_type, featured image, excerpt, ACF/meta fields and the slug copied back, and then Elementor's CSS regenerated against the live post ID, since the copy's CSS file is keyed to the copy's ID. The sharper one: element IDs. Elementor normally rewrites element IDs when a document is duplicated. If your copy gets fresh IDs and promotion writes them over the live post, anything targeting .elementor-element-xxxxxxx breaks - custom CSS, anchor links, popup triggers and sticky/scroll effects that reference an element ID. Preserving IDs through the copy is worth confirming.

The base-revision check

How is the base determined? If it compares post_modified_gmt you'll get false conflicts every time Yoast, a cache warmer or a quick-edit touches the post without changing content. Hashing the live _elementor_data at copy time and comparing on promote is more honest:

Code:
$base = md5( (string) get_post_meta( $live_id, '_elementor_data', true ) );
update_post_meta( $copy_id, '_ellowp_base_hash', $base );
// On promote: compare to the current live hash before writing.

Copies and the admin

Draft copies will show up in Pages lists, admin search, Polylang's language filter (an unassigned copy simply vanishes from the filtered view) and WPML's translation dashboard as untranslated content. Hiding them with a pre_get_posts guard in wp-admin, assigning the source post's language on creation, and sweeping orphaned copies older than N days would keep client dashboards tidy. Also confirm the copy can't be found by search engines if a theme or SEO plugin adds drafts to a sitemap by mistake - unlikely, but a quick noindex on the copy costs nothing.

Staging notes coming once I've tried promoting a Theme Builder header with the editor open on the live version.
 

Latest Resources

Other Elementor Resources

elementor official

Still stuck? Ask the forum

Post your question with a screenshot and a link if you can, and a member will usually reply within a day. Free to join, no sales pitch.

Create a free account
Top