Install
openclaw skills install @benkalsky/elementor-pro-studioHelps with WordPress + Elementor work via the elementor-mcp MCP server — building new pages, editing existing ones, inspecting site state, or exploring what's possible. Auto-detects Elementor Pro (native Form, Theme Builder, Loop Grid, Popups, Dynamic Tags, Sticky/Motion vs free-tier workarounds) AND the page engine (classic vs Elementor 4 atomic/V4 — atomic uses add-flexbox/add-atomic-* tools since classic writes don't persist on a V4 page). Detects ACF + Crocoblock/JetEngine for dynamic-data binding (Tier-0; bind ACF via Pro dynamic tags, place Jet widgets via add-widget with runtime-verified types). Asks what the user wants before acting. Use when the user references the Elementor MCP, invokes /elementor-pro-studio, or runs mcp__elementor__elementor-mcp-* tools. Also covers initial install of the MCP Adapter + elementor-mcp plugins, app-password auth wiring, schema-loading discipline, and the widget-vs-HTML decision tree. SKIP for Bricks, Divi, Beaver Builder, or non-Elementor WordPress builds.
openclaw skills install @benkalsky/elementor-pro-studioYou are operating against a WordPress site with the elementor-mcp server (https://github.com/Digitizers/elementor-mcp — our fork, Elementor 4.x-correct) connected via the WordPress MCP Adapter. This skill captures everything I learned the hard way the first time through, so subsequent sessions start at expertise level.
When this skill is invoked, do not start running tools. Ask the user what they want first.
Shell / setup actions run only on explicit user confirmation. The bundled
setup-elementor-mcp.sh(which shells out, runs Local's WP-CLI, downloads the plugin, and writes a credentialed.mcp.json) is never run automatically — offer it and wait for the user to say yes.
If the user's invocation message already contains a clear task — "build me a hero section from index.html", "show me my current global colors", "change the burgundy to navy" — proceed with that task directly.
Otherwise (invocations like /elementor-pro-studio alone, or "use the Elementor MCP" with no follow-up), respond with this menu and wait for the user to pick:
What would you like to do with your Elementor site?
1. Build — create new pages or sections from a design
2. Edit — change something on an existing page
3. Reference — inspect current state (pages, colors, fonts, content)
4. Explore — show me what's possible / what can the MCP do here
Do not silently default to "build" — that's the most destructive action and forces a path the user may not want. Wait for the user to choose 1/2/3/4 (or describe their task in their own words) before invoking any MCP tool other than the harmless read-only ones at the bottom of this section.
When the user picks any option, you can run these before asking follow-up questions, since they help frame the next response:
mcp__elementor__elementor-mcp-list-pages — confirms auth + lists what's theremcp__elementor__elementor-mcp-get-global-settings — current colors/fonts kitThat's it for unprompted tool calls. Anything that creates, modifies, or deletes data requires the user to have explicitly asked for it.
/elementor-pro-studio, or says "use the Elementor MCP".mcp.json in the project registers an MCP server pointing at wp-json/mcp/elementor-mcp-servermcp__elementor__elementor-mcp-* are availableEngine: this skill drives our fork
Digitizers/elementor-mcp(94 tools, 4.x-correct), a single self-contained plugin. Never run it alongside the paid "MCP Tools for Elementor (Premium)" — same class names → fatal. Details + switch commands →references/engine-and-premium.md.
If the user has a WordPress site but no .mcp.json and no elementor MCP loaded:
~/.claude/scripts/setup-elementor-mcp.sh — it handles plugin install, auth wiring, and .mcp.json generation interactively for both flavors.
bash ~/.claude/scripts/setup-elementor-mcp.sh
.mcp.json is picked up.select: queries.If something fails, see "Setup gotchas" below.
mcp__elementor__elementor-mcp-list-pages # confirms auth + lists existing pages
mcp__elementor__elementor-mcp-get-global-settings # see existing colors/fonts kit
mcp__elementor__elementor-mcp-get-container-schema # ground truth on flex_* key names
Before building anything, determine whether Elementor Pro is active. The whole skill branches on this: with Pro you use native widgets (Form, Theme Builder, Loop Grid, Popups, Dynamic Tags, Sticky/Motion); without it you use the free-tier workarounds documented further down (Fluent Forms, UAE/HFE, HTML for motion).
How to detect — by tool availability, not the buggy version tool:
The elementor-mcp server exposes Pro tools conditionally. When Pro is active
the tool list grows from ~74 to ~100+ tools and Pro-only tools appear. Check
whether these exist in your available mcp__elementor__elementor-mcp-* tools:
add-form — present ⇒ Pro activecreate-theme-template — present ⇒ Pro activeadd-loop-grid / add-loop-carousel — present ⇒ Pro activecreate-popup, set-dynamic-tag — present ⇒ Pro activeIf none of those Pro tools are exposed, treat the site as Free and use the
workarounds. Do NOT call detect-elementor-version — it errors in v1.5.0
(elementor_pro_version null vs. schema string, see Setup gotchas).
Record the verdict once ("Pro detected" / "Free only") and state it to the user up front, then follow the matching branch in every section below. Each "Forms", "Header/Footer", and motion section is written as If Pro → … / If Free → …. Don't mix paths.
There's a second axis that changes everything: the page engine. Elementor 4
introduced the atomic / V4 engine (e-flexbox, e-div-block, atomic
widgets, a typed-prop $$type data model). Classic widget writes do not
persist on an atomic page — they silently appear to do nothing. So before
building, decide classic vs atomic.
How to detect — by atomic tool availability (preferred):
The atomic tools register only when the site is on the atomic engine. Check your available tools:
add-flexbox, add-div-block, add-atomic-heading / add-atomic-button / …,
add-atomic-widget / update-atomic-widget present ⇒ atomic (V4) engineadd-container / add-heading / … present ⇒ classic engineYou may also call detect-elementor-version (reliable on current releases — it
returns whether atomic is supported). The old v1.5.0 schema bug is fixed; still,
tool-presence is the most direct signal.
⚠️ Antigravity / tight tool caps. Antigravity caps MCP tools at ~100. The full Pro+atomic set is ~113, so atomic tools can get truncated and never reach the client — making a V4 site look like "writes don't persist". Fix: enable the MCP plugin's Low-tools mode (WP Admin → MCP Tools screen). Its curated essentials set includes the 5 atomic essentials (
detect-elementor-version,add-atomic-widget,update-atomic-widget,add-flexbox,add-div-block) and stays under the cap.
🐞 Known root cause (older MCP builds). Elementor often runs atomic as an opt-in experiment while
ELEMENTOR_VERSIONstill reads3.x. MCP builds that gate atomic onversion_compare(ELEMENTOR_VERSION,'4.0.0','>=')therefore never register the atomic tools on those sites. Fixed upstream by detecting via the experiment/module. If atomic tools are missing on a clearly-V4 site, update the elementor-mcp plugin (or confirm the V4 experiment is on under Elementor → Settings → Features).
Record both axes: e.g. "Pro + atomic", "Pro + classic", "Free + classic". The Pro/Free axis picks Form vs Fluent Forms etc.; the engine axis picks classic vs atomic widget tools (next section).
The container schema is large (~50KB). Read it once, then write down the keys you'll use in your reply text so you don't need to re-fetch it. Critical keys:
flex_direction, flex_justify_content, flex_align_items, flex_gap, flex_wrap — note the flex_ prefix on justify/align (issue #32 was about these being written under wrong keys in older versions)content_width: "boxed"|"full" + boxed_width: {unit, size, sizes}min_height: {unit, size, sizes} — use unit vh for full-screen heroespadding/margin: {unit, top, right, bottom, left, isLinked} — isLinked: false when sides differbackground_background: "classic"|"gradient"|"video" — must be set first or other background_* keys are ignoredbackground_overlay_* — separate parallel set for overlays. background_overlay_opacity: {unit:"px", size: 0.5} (yes, the unit is px even for opacity — quirk of the schema)settingsThis bit me hard the first time. The add-* shortcut tools take their settings as top-level parameters, not inside a settings: {} object:
// ✓ CORRECT
mcp__elementor__elementor-mcp-add-heading({
post_id: 11,
parent_id: "abc123",
title: "where estates <em>are entrusted</em>",
header_size: "h1",
title_color: "#FFFFFF",
typography_typography: "custom", // ← required to enable typography
typography_font_family: "Cormorant Garamond",
typography_font_size: {size: 110, unit: "px"},
typography_font_weight: "300",
typography_line_height: {size: 0.98, unit: "em"},
})
// ✗ WRONG — silently fails or returns "title is required"
mcp__elementor__elementor-mcp-add-heading({
post_id: 11,
parent_id: "abc123",
settings: {title: "...", typography_font_family: "..."}
})
add-container is the exception — it takes a settings: {} object. Don't generalize from one to the other.
typography_typography: "custom"Without this, the other typography_* keys are ignored. Same applies to css_filters_css_filter: "custom" for image filters, etc. — these "enable" flags are how Elementor knows you want to override defaults.
Display headings often need a single italic-emphasized word. Don't use a separate widget — just inline <em> in the title:
title: "A <em>quiet</em> practice for an <em>uncommon</em> clientele."
Cormorant Garamond and most luxury serifs have italic variants that auto-load when <em> appears. Confirm via the rendered page; if italics fail, the global typography needs the italic variant explicitly enabled.
🚨 CRITICAL ANTI-PATTERN — read this first.
Do NOT paste an entire HTML page into one HTML widget. Do NOT build a homepage that is "1 container with 3 HTML widgets inside." That is not building with Elementor — that is using Elementor as a wrapper around a static webpage. The user cannot edit it in the Elementor visual editor, cannot reuse the design tokens, and cannot iterate on it without going back to source code.
If you find yourself thinking "I'll just dump this section as HTML, it's faster," STOP. Break it into native widgets.
For every section the user wants, build it from native Elementor widgets:
add-heading widget (supports inline <em> for italic emphasis)add-text-editor widgetadd-image widget (NOT an <img> tag inside an HTML widget)add-button widget (NOT an <a> styled as a button)add-container with proper flex_* settings (NOT <div>s with CSS flex)add-icon-list widgetadd-tabs widgetadd-accordion widgetadd-shortcode widgetuael-nav-menu)Only reach for an HTML widget in these specific cases. Anything not on this list goes through native widgets.
add-tabs only accepts tab_content as a string of HTML, so a multi-card grid inside a tab MUST be HTML. (But the wrapping Tabs widget itself is still native.)add-tabs widget that the widget controls don't expose). These should be small style blocks, not whole sections of markup.Earlier versions of this skill said "use one HTML widget for card grids — it's faster than 50 widget calls." That advice was wrong because it led to non-editable pages.
The correct path for card grids:
duplicate-element to copy it 3+ more timesupdate-element to change the copy/image on each duplicateflex_direction: row and flex_wrap: wrapThis is more widget calls, yes, but the result is a real Elementor card grid the user can edit, restyle globally, or reuse as a template.
If Pro is active and the cards are driven by posts/CPT/products (a blog feed, portfolio, listings), prefer the native Loop Grid (
add-loop-grid) instead — see the Loop Grid section below. The duplicate-element pattern is still the right answer for a fixed set of bespoke, non-dynamic cards on either tier.
<style>-only HTML widgetsWhen you need to style a native widget from outside (e.g., overriding the Tabs widget tab strip styles that the widget controls don't expose), use a <style>-only HTML widget: it contains ONLY a <style> block — no markup, no rendered content. Scope every selector to the parent Elementor element ID:
<style>
.elementor-element-f8d1545 .elementor-tab-title {
text-transform: uppercase !important;
letter-spacing: .26em !important;
}
.elementor-element-f8d1545 .elementor-tab-title.elementor-active {
border-bottom-color: #171615 !important;
}
</style>
The f8d1545 is the element_id returned when you created the tabs widget. Always grab and remember these IDs — they're the only stable selector across page reloads.
⚠️ An HTML widget used for cross-widget styling MUST contain only
<style>. If you find yourself adding HTML markup (divs, anchors, spans with text content) alongside the style block, you're falling back into the anti-pattern at the top of this section. Stop. That markup belongs in native widgets.
Elementor 4 uses an atomic/V4 data model — classic widget writes don't persist on a V4 page. Detect the engine first (see core detection above), then use the atomic tool family. Full atomic model, tool family, and build order → load references/atomic-v4.md.
Triggers when the user is setting up a new client / brand, or says "set up the
brand". Establishes the design tokens every later build references by name, never
raw hex/font. Full schema + tool shapes: references/brand-kit.md.
The 8 color tokens (brand, accent, heading, text, bg, surface, muted, border) and
2 font tokens (heading-font, body-font) map to named Elementor custom globals.
Flow:
surface = tint of bg; muted = lower-contrast text; border = light
grey) and state the derivation.update-global-colors with the 8 {_id, title, color} entries, then
update-global-typography with the 2 font entries. (Exact payloads in the
reference.)get-global-settings shows the 8 colors + 2 fonts by name.After intake, bind widget colors to these globals (or use the recorded token value when setting directly). Introducing an ad-hoc hex/font mid-build breaks brand consistency — don't.
Reusable, brand-token-driven build sequences for common sections. Before building a
section, consult the matching recipe and bind everything to the brand tokens (see
"Brand kit" above). Classic-first; apply the recipe's Pro/V4 variant note when those
engines are active. Full trees + token bindings: references/recipes.md.
Available recipes: Hero, Services grid, Split (image + text), Stats band, Testimonials, CTA band, Contact, FAQ, Pricing, Logos strip. (The library grows — add a recipe when a new section type recurs.)
Recipes reuse the rest of this skill's rules (native widgets not HTML dumps,
duplicate-element/Loop Grid for grids, flat-param convention) — they don't restate
them.
Studio voice default: clean, confident, conversion-focused; real copy (no lorem); accessible contrast; consistent spacing scale. Per-client tone comes from the matched vertical (see references/verticals/).
Vertical routing: if the client matches a known vertical, load its pack first for voice + design system + section flow: references/verticals/{dental,salon,car-wash,local-business,portfolio}.md. No match → proceed with the studio voice default + the recipe library.
Use this section only when the user has explicitly asked you to build something. Do not run this flow on a bare
/elementor-pro-studioinvocation.
For a new page, build top-down section by section, in small commits, verifying after each:
get-global-settings.create-page({title, status: "publish", template: "elementor_canvas"}) — Canvas template removes theme header/footer chrome so your design is the only thing on the pagewp option update show_on_front page; wp option update page_on_front <id>get-page-structure(post_id) to verify nesting, or just curl the front pageApproach existing pages surgically — don't rebuild what you don't have to:
list-pages to find the page they're editingget-page-structure(post_id) to see the current widget tree and grab element IDsfind-element if needed, then update-element with only the fields that changeget-page-structure or curling the rendered pagemove-element or update-element first.Read-only tools, no writes. Useful for "show me", "tell me", "what's", "list" requests:
list-pages — what pages existget-global-settings — colors, typography, layout settingsget-page-structure(post_id) — what's on a pageget-element-settings(element_id) — exact settings of one widgetfind-element(post_id, ...) — locate a widget by content/typeFormat the response as a clear summary, not a JSON dump. The user wants understanding, not raw data.
Give a short menu (don't dump all 75 tools). Point them at the four modes from the First Action Protocol with concrete examples:
Then ask which mode they want.
Theme Builder (Pro) is the preferred header/footer path; UAE/HFE is the free fallback. Full patterns (Theme Builder vs UAE/HFE, nav menu, site-wide header/footer) → load references/header-footer.md.
When Pro is active, native widgets beat HTML: Loop Grid/Carousel, Popups, Dynamic Tags, Sticky header + Motion Effects. Full per-feature guidance → load references/pro-widgets.md.
Bind ACF via Pro dynamic tags; place Jet widgets via add-widget with runtime-verified types. Tier-0 scope only. Full ACF + Crocoblock/JetEngine guidance → load references/dynamic-data.md.
If Pro → native Form widget (preferred). If free → Fluent Forms (fallback). Full form guidance (native Form settings, Fluent Forms class map, alternatives) → load references/forms.md.
admin or test or whatever they set up. If curl -u "ClaudeMCP:..." returns 401, try curl -u "admin:..." or check GET /wp-json/wp/v2/users to find the real slug.wp-config.php says DB_HOST=localhost but the real MySQL is on a per-site Unix socket. WP-CLI fails with "Error establishing a database connection" until you pass -d mysqli.default_socket=/path/to/mysqld.sock. The setup script handles this; if doing it manually, find the socket via find ~/Library/Application\ Support/Local/run -name mysqld.sock.Digitizers-elementor-mcp-<sha>/). WordPress uses the folder name as the plugin slug. Repack with a clean elementor-mcp/ folder before installing..mcp.json at startup — after writing one, the user must quit and reopen.detect-elementor-version tool errored with a schema validation bug in v1.5.0 (elementor_pro_version null vs. schema string). Fixed in current builds and useful for the classic-vs-atomic check — but for the plain auth-works smoke test, list-pages is still the simplest.ELEMENTOR_VERSION >= 4.0.0, but Elementor runs atomic as an experiment while the constant still reads 3.x — so the tools never register and classic writes silently don't persist. Update the elementor-mcp plugin (the detection now keys off the atomic experiment/module), and on tight tool caps (Antigravity) enable Low-tools mode so the 5 atomic essentials stay exposed. See the engine-detection section up top.Local-by-Flywheel: Plugin install via the bundled WP-CLI binary at /Applications/Local.app/Contents/Resources/extraResources/bin/wp-cli/posix/wp with PHP at ~/Library/Application Support/Local/lightning-services/php-*/bin/darwin-arm64/bin/php and the per-site MySQL socket. The setup script automates all of this.
Live host (cPanel/Cloudways/Kinsta/etc.): Plugin install via WP Admin → Plugins → Add New → Upload Plugin (manual upload of the two zips). Auth is the same — REST API + Application Password. MCP URL changes to https://<live-domain>/wp-json/mcp/elementor-mcp-server. Important: if the live site is HTTPS (it should be), make sure curl/Claude Code can reach it from your local machine — some hosts block non-browser User-Agents on /wp-json/. The setup script's "live" path tests this with a single curl before writing .mcp.json.
The MCP exposes ~75 deferred tools. Don't load them all at once — fetch schemas lazily as you build:
list-pages (no schema needed — pre-loaded by ToolSearch when triggered)get-container-schema, add-container, update-containeradd-heading, add-text-editor, add-button, add-image, add-html in one batchadd-tabs, add-icon-list, add-divider, add-spacer as neededUse ToolSearch query format select:tool1,tool2,tool3 to load multiple in one call.
wp option update)create-theme-template)Pro features are NOT a limitation when Pro is active — Form widget, Theme Builder, Loop Grid, Popups, Dynamic Tags, and Sticky/Motion are all driven natively (see the Pro sections above). They're only unavailable on the Free tier, where the documented workarounds apply.
wordpress-api-pro (content/SEO/commerce ops)These are separate, opt-in tools — not a capability of this skill. This skill drives the Elementor MCP only. The companion toolkit below is a different project with its own credentials and permissions, and you should reach for it only when the user explicitly asks for one of the content/SEO/media/ACF/WooCommerce tasks it covers. Do not silently invoke it, and do not treat its abilities as automatically available here.
The Elementor MCP is for building and editing page structure (containers, widgets, Pro widgets). It does not cover bulk content ops, media-library uploads, SEO metadata, custom fields, or WooCommerce. When the user asks for one of those, a sibling toolkit — wordpress-api-pro (Python REST scripts, App-Password auth) — fills the gaps. It authenticates with its own environment-based credentials (WP_URL / WP_USERNAME / WP_APP_PASSWORD), separate from this skill's .mcp.json, though it can target the same site.
This skill is one stage of the studio toolbox (audit → build → content → host → ads). Full handoffs + a "where am I" router → load references/lifecycle.md.
When the user explicitly asks for one of these, wordpress-api-pro is the right tool instead of the MCP:
| Task | Script |
|---|---|
| Upload an actual image/file to the media library (then feed its URL/ID to an Elementor Image widget) | upload_media.py |
| Read/write SEO meta (Rank Math / Yoast) | seo_meta.py |
| Read/write ACF or JetEngine custom fields | acf_fields.py / jetengine_fields.py |
| List/create/update WooCommerce products | woo_products.py |
| Bulk content changes across many posts or multiple sites (dry-run first) | batch_update.py, wp.sh |
| Plain post/page CRUD outside Elementor | create_post.py / update_post.py / get_post.py / list_posts.py |
Division of labor: build the page with the MCP → upload media + set SEO meta + wire custom fields/products with wordpress-api-pro. Both touch _elementor_data, but prefer the MCP for structured Elementor edits and reserve wordpress-api-pro's elementor_content.py for scripted/batch field tweaks.
Setup: the scripts need Python 3.8+ and
requests(pip install requests, or a venv). Auth viaWP_URL/WP_USERNAME/WP_APP_PASSWORDenv vars, orconfig/sites.jsonfor multi-site. See that repo'sSKILL.md.
Use this flow only after the user has explicitly chosen "Build" or asked to build a new site/page. Do not run this flow as a default response to
/elementor-pro-studio— see the First Action Protocol at the top.
1. setup-elementor-mcp.sh # one-time, ~3 minutes
2. Quit + reopen Claude Code # picks up .mcp.json
3. list-pages # confirm auth
4. get-global-settings # see current kit
5. update-global-colors + typography
6. create-page (Elementor Canvas template)
7. Set as front page via WP-CLI
8. Build sections top-down, one at a time
9. After each: get-page-structure or curl the front page
10. Pause for human review before header/footer
When working from a designed HTML mockup, map the source design to Elementor like this:
update-global-colorsupdate-global-typographyadd-heading + add-text-editor widgetsduplicate-element and update-element per copyadd-tabs/add-accordion widgets (HTML allowed inside tab_content strings only — see anti-pattern section)add-shortcode widget (see Fluent Forms section)elementor-hf post type with UAE Nav Menu widget for nav🚨 Final reminder: Default to native widgets. The HTML widget is only for the four narrow cases listed in the anti-pattern section. Never paste a complete page section as raw HTML — the user must be able to edit the result inside Elementor.