Data Structures

Understanding Elementor's Data Structure

⏱ 16 min

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.

What you'll learn in this lesson
The _elementor_data meta key
Where Elementor actually stores a page's layout, and why post_content is mostly irrelevant here.
The top-level JSON shape
title, type, version, page_settings, and content, and what each holds.
The recursive element shape
id, elType, isInner, settings, elements, and widgetType, and how nesting works.
Why you must inspect your own site's data
Section/column structures versus newer flexbox containers store differently.
Prerequisites

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:

Elementor’s top-level data structure
FieldPurpose
titleThe document’s title as shown in the Elementor editor.
typeThe document type: page, post, header, footer, popup, and others.
versionThe data structure’s own version number, currently 0.4 per Elementor’s documentation.
page_settingsPage-wide settings from the editor’s Page Settings panel.
contentAn 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.

Section/column versus container: check your version

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.

A stale rendered CSS cache after editing JSON directly

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

← Why Core MCP Tools Stop at the Page Builder Boundary Understanding Divi's Data Structure →