--- 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.