From 8f34371a9f8dbeb2265e92bb1ef5d64c378b7712 Mon Sep 17 00:00:00 2001 From: Malin Date: Thu, 13 Aug 2026 18:16:39 +0200 Subject: [PATCH] skills: add wordpress-elementor-safe-cli-editing Evaluated the Stonewright WP MCP repo (github.com/cosmincraciun97/ stonewright-wp-mcp) per user request -- decided against adopting it or its MCP server (this fleet already does WP work via shell+WP-CLI, and its Gutenberg layer is thin wrapping over parse_blocks/serialize_blocks we can already call directly). Its Elementor V3 _elementor_data read/write pipeline is genuinely well-engineered, non-obvious, and worth having as a reference: double-encoding trap, wp_slash-before-persist, surgical patches that preserve unrecognized settings keys, and readback+cache-clear+visual verification after write. Distilled into this skill for CLI/wp-eval-based Elementor edits (not the MCP itself). --- .../SKILL.md | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 skills/wordpress-elementor-safe-cli-editing/SKILL.md diff --git a/skills/wordpress-elementor-safe-cli-editing/SKILL.md b/skills/wordpress-elementor-safe-cli-editing/SKILL.md new file mode 100644 index 0000000..f502d78 --- /dev/null +++ b/skills/wordpress-elementor-safe-cli-editing/SKILL.md @@ -0,0 +1,143 @@ +--- +name: wordpress-elementor-safe-cli-editing +description: Use when editing an Elementor page/section via WP-CLI or a raw PHP snippet (wp eval, wp eval-file) instead of the Elementor editor UI — reading and mutating the `_elementor_data` JSON tree safely. Covers the double-encoding trap, wp_slash, unknown-setting stripping, and verifying a write actually took. +--- + +# WordPress Elementor Safe CLI Editing + +Elementor stores a page's layout as a JSON array in the `_elementor_data` +post meta key — a tree of `{id, elType, settings, elements, widgetType?}` +nodes. Editing it directly via `wp eval`/`wp eval-file`/a REST call is much +faster than driving the visual editor, but the format has several sharp +edges that silently corrupt the page if you're not careful. This skill is +the CLI-safe way to do it. For how to actually invoke `wp` against a +site living in a remote jail, see `wordpress-cli-remote-execution` first. + +Credit: these techniques are distilled from reading the (AGPL-3.0) +[Stonewright WP MCP](https://github.com/cosmincraciun97/stonewright-wp-mcp) +plugin's `ElementorData` read/write pipeline — a real, tested Elementor +integrity layer — not reinvented from scratch. This fleet doesn't run +Stonewright itself (no MCP dependency), just the underlying technique, +applied via plain WP-CLI/PHP. + +## Reading: handle the double-encoding trap + +`get_post_meta($id, '_elementor_data', true)` can return either a real +array (rare) or a JSON string — and that JSON string can itself already be +double-encoded (a JSON string whose *decoded value* is itself a JSON +string) if some prior tool re-encoded an already-encoded value. Decode, +and if the result is still a string, decode once more: + +```php +$raw = get_post_meta( $post_id, '_elementor_data', true ); +$tree = is_array( $raw ) ? $raw : json_decode( (string) wp_unslash( $raw ), true ); +if ( is_string( $tree ) ) { // was double-encoded + $tree = json_decode( $tree, true ); +} +if ( ! is_array( $tree ) ) { + // empty/missing document — treat as [] + $tree = []; +} +``` + +Read for convenience, but **never write the double-encoded form** — that's +how documents end up double-encoded in the first place (see below). + +## Editing: surgical patch, not full-tree rewrite + +Don't decode the whole tree, hand-edit it broadly, and write the whole +thing back to fix one control. Find the specific element by `id` (or by +walking `elements` for a matching `elType`/`widgetType`), mutate only the +keys you actually intend to change, and leave everything else — including +settings keys you don't recognize — untouched. + +**Why this matters:** Elementor's settings schema grows over time and +varies by widget version/Pro-vs-free. A setting your script doesn't +recognize isn't dead weight to strip on the way to "clean" JSON — it's +real configuration (padding, a Pro feature's data, a third-party +extension's data) that a full rewrite through your own limited +understanding of the schema will silently drop. + +**Never change `widgetType`** unless that's explicitly the intent — e.g. +`e-paragraph` → `text-editor` look similar but are different widgets with +different settings shapes; changing it without care produces a widget that +renders wrong or not at all. + +When you need a **new** element's `id`, don't use a random string — +Elementor IDs are conventionally short (7-char) hex strings. Deriving them +deterministically (e.g. `substr(sha1($stable_key_path), 0, 7)`) means +re-running the same generation logic produces the same ID, which makes +future diffs/updates to that element addressable instead of having to +re-discover it by content matching. + +## Writing: the encode/persist sequence, exactly + +```php +$json = wp_json_encode( $tree, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); +update_post_meta( $post_id, '_elementor_data', wp_slash( $json ) ); +update_post_meta( $post_id, '_elementor_edit_mode', 'builder' ); +update_post_meta( $post_id, '_elementor_version', defined('ELEMENTOR_VERSION') ? ELEMENTOR_VERSION : '3.0.0' ); +``` + +Three things people get wrong here, in order of how often they bite: + +1. **`wp_slash()` before `update_post_meta()`, always.** WordPress's meta + APIs expect slashed input and will mangle quotes/backslashes in your + JSON on save if you pass it unslashed — this is the classic root cause + of a page that "saved fine" but renders broken or throws a JS parse + error in the Elementor editor afterward. +2. **`JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE`** — without these, + `wp_json_encode` escapes `/` and non-ASCII characters, which Elementor's + own JS-side parser doesn't expect in the same normalized form it writes + itself; it's usually harmless but produces spurious diffs that make + real changes hard to review, and can trip the double-encoding check on + a subsequent read if another tool round-trips it. +3. **Update all three meta keys together**, not just `_elementor_data` — + a stale `_elementor_version` after a plugin update, or a missing + `_elementor_edit_mode`, can make Elementor treat the document as + needing conversion/repair on next edit. + +## After writing: readback is not verification + +A successful `update_post_meta()` call only means the write didn't error — +it doesn't mean the stored value round-trips to what you intended (a +serialization bug or a WordPress filter mutating meta on save would both +still return success). Always: + +1. **Read the meta back** and compare it (structurally, e.g. by hashing + the decoded tree) against what you tried to write. A quick hash + comparison catches "nothing actually happened" or "something else + silently changed it" without a full diff. +2. **Clear Elementor's CSS cache** — otherwise the page keeps rendering + old generated CSS. From a PHP/`wp eval` context: + `\Elementor\Plugin::$instance->files_manager->clear_cache();` + From pure WP-CLI without PHP eval, use `wp elementor flush-css` if the + site has WP-CLI Elementor commands available, or clear + `wp-content/uploads/elementor/css/post-.css` directly and let it + regenerate on next view. +3. **Load the actual page in a real browser** and check it — meta + readback alone is not completion. Check desktop, tablet, and mobile + breakpoints (Elementor's responsive settings live in the same tree + under per-breakpoint keys and are easy to verify wrong from source + alone). For a boxed/contained section, check both the outer element + *and* its `.e-con-inner` — a padding or width fix applied to only one + of the two is a common half-fix that looks right in the tree and wrong + on screen. + +## Before touching a page at all: back it up + +Elementor pages are WordPress posts — a plain `wp post` revision (or even +just reading and stashing the current `_elementor_data` value yourself +before writing) gives you a real rollback path if the edit goes wrong. +Don't skip this because the change "looks small" — a bad write to +`_elementor_data` can make the page fail to load in the Elementor editor +at all, at which point the only fix is restoring the meta value. + +## Don't run two edits to the same page concurrently + +If more than one process/session might edit the same post around the same +time, treat each post's edit as serialized: read → mutate → write → verify +as one uninterrupted sequence, not overlapping with another edit to the +same post. Concurrent writes to the same `_elementor_data` key are a +last-write-wins race with no merge — whichever write lands second silently +discards the first.