Before writing an ability that touches Elementor content, you need to know exactly what Elementor stores and where. This lesson covers the real, documented structure, verified against Elementor’s own developer documentation, plus the version differences that make “just parse the JSON” more nuanced than it first sounds. Lesson 4 builds the actual ability on top of what you learn here.
Lesson 1’s framing of the gap. A WordPress site with Elementor active and at least one page built with it, so you can inspect real, current data rather than a hypothetical example.
Step 1: Where the data actually lives
Elementor stores a page’s layout as JSON in a single post meta field, keyed
_elementor_data, on the post itself. This is a real, well-established convention, not
an implementation detail likely to change without notice, and you can confirm it on any
Elementor-built post right now:
# File: terminal
wp post meta get <post_id> _elementor_data
You’ll get back a long JSON string. post_content on the same post is largely
irrelevant to the actual layout, Elementor keeps it in sync mostly for backward
compatibility (search, RSS feeds, plugins that read post_content directly), the source
of truth for what renders on the front end is _elementor_data.
Step 2: The top-level JSON shape
According to Elementor’s own developer documentation, the decoded JSON is an object with five top-level fields:
| Field | Purpose |
|---|---|
title | The document’s title as shown in the Elementor editor. |
type | The document type: page, post, header, footer, popup, and others. |
version | The data structure’s own version number, currently 0.4 per Elementor’s documentation. |
page_settings | Page-wide settings from the editor’s Page Settings panel. |
content | An array of element objects. This is the actual layout tree, and it’s recursive. |
Step 3: The recursive element shape
Every object inside content (and inside any element’s own nested elements array)
shares the same basic fields:
{
"id": "3f2a91c",
"elType": "container",
"isInner": false,
"settings": {
"background_background": "classic",
"background_color": "#f7f7f7"
},
"elements": [
{
"id": "8b4e027",
"elType": "widget",
"widgetType": "heading",
"isInner": false,
"settings": {
"title": "Welcome to our site",
"header_size": "h1"
},
"elements": []
}
]
}
id is a short, stable identifier Elementor assigns to every element and keeps stable
across saves, this is the value you’ll match against in Lesson 4, never a numeric
position in an array. elType says what kind of element it is, layout elements
(sections, columns, or containers depending on version) versus widget. settings is
where the actual content and styling live, a heading widget’s visible text sits in
settings.title in the example above, but the exact key varies by widget type, always
confirm the real key for a given widget by inspecting your own saved data rather than
guessing. elements holds children and is empty for leaf widgets. widgetType only
appears when elType is "widget", naming which specific widget (heading, image,
button, and so on) this is.
Elementor has shipped two different layout structures over time. Older sites (and sites
that haven’t opted into Elementor’s newer flexbox containers) nest elements as
section containing column containing widget. Newer sites use a single container
elType that can nest directly inside itself, collapsing what used to take three levels
into one. Both are real, currently valid structures, which one you’ll find depends on
when a given page was built and whether the site has enabled Elementor’s container
experiment or made it default. Don’t assume either shape, inspect the actual
_elementor_data on the page you’re building an ability against.
Step 4: Re-saving the JSON without corrupting it
Once you decode _elementor_data with json_decode( $raw, true ), modify the array,
and re-encode it, there’s a real, documented WordPress behavior to account for:
WordPress’s meta-saving functions run incoming data through wp_unslash()-adjacent
logic that strips backslashes as if they’d been added by “magic quotes.” A JSON string
containing escaped characters (an apostrophe inside a heading’s text, for instance) can
lose those backslashes on save if you don’t call wp_slash() on the encoded string
first. Use wp_json_encode() rather than raw json_encode() to match how WordPress
itself encodes JSON for storage, and wrap the result in wp_slash() before passing it
to update_post_meta(). Lesson 4 shows this in a complete, working ability.
Test it: inspect your own site’s real structure
Don’t take this lesson’s example JSON as gospel for your site, confirm it directly:
# File: terminal
wp post meta get <post_id> _elementor_data | python3 -m json.tool | less
Look specifically at the elType values you actually see, and note whether it’s
section/column/widget or container/widget, that distinction determines how
your Lesson 4 ability walks the tree.
If you edit _elementor_data outside the Elementor editor (which is exactly what
Lesson 4’s ability does) and the front end doesn’t reflect your change, Elementor may
be serving cached CSS generated from the old data. Elementor’s admin has a
Elementor > Tools > Regenerate CSS action for this. The underlying regeneration can
also be triggered programmatically, but the exact class and method names are internal
implementation details that have shifted between Elementor versions, verify against the
Elementor version actually installed before relying on any specific function call in
production code.
Recap
Elementor stores a page’s real layout as JSON in the _elementor_data post meta field,
structured as a top-level object with title, type, version, page_settings, and a
recursive content array. Each element carries a stable id, an elType, a settings
object, and a nested elements array, with widgetType added for widgets specifically.
The exact nesting depends on whether a page uses the older section/column structure or
newer containers, and re-saving the JSON safely requires wp_json_encode() and
wp_slash() to avoid WordPress stripping backslashes on the way into the database.
Resources & further reading
- Elementor Developers: Data Structure overview, developers.elementor.com
- Elementor Developers: General Structure, developers.elementor.com
- Elementor Developers: General Elements, developers.elementor.com
- wp_json_encode() function reference, developer.wordpress.org