Support / Royal MCP Pro / Divi 5 Authoring

Divi 5 Authoring — Programmatic Block Schema

Divi 5 modules can be authored programmatically. This doc covers the 5 requirements Divi silently enforces on block content, the double-quote gotcha every author eventually hits, the reverse-engineer-by-diff method for figuring out any undocumented block, and the divi5_scaffold_block helper tool that collapses all of it into one call.

Fast path: use divi5_scaffold_block

If you just want to write a Divi 5 code, text, or heading module into a post and don't need to know why the schema is what it is, jump straight to the scaffold tool. It handles all five requirements + the quote gotcha automatically. The rest of this doc explains the underlying schema so you can build against it directly when the scaffold isn't enough.

Prerequisites

  • Divi 5 active on the target site (Divi 5.9.0 or later — earlier D5 releases use a slightly different attribute shape)
  • Royal MCP Pro installed and licensed (see Getting Started)
  • MCP client connected to your site (Claude Desktop, claude.ai custom connector, Cursor, or any MCP-compatible client)

If your site is on Divi 4, use divi_prepare_shortcode_migration to prepare the migration before authoring new D5 content. (The prior tool name divi_convert_shortcodes_to_blocks is still accepted as a deprecated alias.) New D5 content on a D4 site renders correctly per-post, but authoring on a fully-migrated D5 site is where the schema below is meant to be used.

The 5 requirements Divi silently enforces

Every Divi 5 module block has to satisfy five constraints. Any single one wrong and the module renders empty — no error, no warning, no message in the editor. The post body is just blank. These are the requirements to know:

  1. Wrap the document in wp:divi/placeholder. This is the root marker Divi looks for. Without it Divi doesn't treat the post as a D5 layout at all and never even attempts to render the modules inside.
  2. builderVersion attribute on every block. Section, row, column, and each module. When the attribute is missing or wrong, Divi assigns the fallback string 5.0.0-public-alpha.18.2 and renders nothing. This is the single largest cause of empty D5 bodies we've seen.
  3. Content-bearing modules use object-shaped content. A code, text, or heading module's content isn't a flat string — it's an object shaped as {innerContent: {desktop: {value: "<your HTML>"}}}. Every attribute in D5 is per-breakpoint (desktop / tablet / phone); pass a flat string and Divi silently drops it.
  4. Row and column carry structure attributes. A row needs columnStructure and flexColumnStructure. A column needs type and flexType. Without them the layout doesn't form, even if the modules inside are perfectly shaped.
  5. Single quotes for HTML attributes inside the payload. A raw double quote inside content terminates the JSON block-attribute payload — Divi's parser sees the " and treats it as the end of the attribute string, breaking the block. Raw <, >, and & pass through fine; only " is a problem. Use single quotes for href/src/class, or HTML-entity-encode as &quot;.
Silent-fail semantics

Every one of these fails invisibly. The post saves without error. The editor doesn't warn. The frontend just renders empty. If you're seeing an empty Divi 5 body where you expected content, check these five in order — most bugs are #2 (wrong or missing builderVersion) or #5 (a literal " made it into content).

The verified schema

Minimum viable Divi 5 post with a single Code module. This exact shape has been verified to render on Divi 5.9.0 (no child theme). Read every attribute carefully — each one is load-bearing.

<!-- wp:divi/placeholder --><!-- wp:divi/section {"builderVersion":"5.9.0"} -->
<!-- wp:divi/row {"module":{"advanced":{"columnStructure":{"desktop":{"value":"4_4"}},
"flexColumnStructure":{"desktop":{"value":"equal-columns_1"}}},"decoration":{"layout":
{"desktop":{"value":{"flexWrap":"nowrap"}}}}},"builderVersion":"5.9.0"} -->
<!-- wp:divi/column {"module":{"advanced":{"type":{"desktop":{"value":"4_4"}}},"decoration":
{"sizing":{"desktop":{"value":{"flexType":"24_24"}}}}},"builderVersion":"5.9.0"} -->
<!-- wp:divi/code {"content":{"innerContent":{"desktop":{"value":"HTML HERE"}}},
"builderVersion":"5.9.0"} /-->
<!-- /wp:divi/column -->
<!-- /wp:divi/row -->
<!-- /wp:divi/section --><!-- /wp:divi/placeholder -->

What each layer does

LayerPurposeRequired attributes
wp:divi/placeholderRoot D5 markerNone
wp:divi/sectionTop-level containerbuilderVersion
wp:divi/rowHorizontal band inside a sectionbuilderVersion, module.advanced.columnStructure, module.advanced.flexColumnStructure
wp:divi/columnVertical column inside a rowbuilderVersion, module.advanced.type, module.decoration.sizing.flexType
wp:divi/code (and other content-bearing modules)The actual contentbuilderVersion, content.innerContent.desktop.value

Other module types

Verified working against Divi 5.9.0:

  • wp:divi/code — content-bearing, uses content.innerContent.desktop.value (the case documented above)
  • wp:divi/text — content-bearing, same content.innerContent.desktop.value shape as code
  • wp:divi/heading — content-bearing, uses title.innerContent.desktop.value (heading modules use a title field in the builder UI rather than content)
  • wp:divi/post-content — structural, renders the current post/page body inside a Theme Builder template with nothing but builderVersion
  • wp:divi/blog — structural, renders category archive listings with builderVersion plus one loop-scoping attribute under the post namespace (see the next section — the namespace is not module)

The divi5_scaffold_block tool below first-class-supports all five of these — you don't need to write the block markup yourself for any of them.

For any other module type (image, button, gallery, divider, audio, video, cta, blurb, testimonial, social-follow, and the rest of Divi's built-in library), the reverse-engineer-by-diff method below is the fastest way to derive the correct attribute shape.

Structural modules — post-content and blog

Structural modules render context rather than caller-supplied content. They're the modules you use inside Divi Theme Builder templates. Two are first-class-scaffolded:

wp:divi/post-content

Renders the current post or page body inside a Theme Builder body template. Used when you want a single template that wraps every post's content in your custom section/row/column layout.

Attributes: just builderVersion. There's no content argument — the module reads from whichever post is currently being viewed.

<!-- wp:divi/post-content {"builderVersion":"5.9.0"} /-->

wp:divi/blog

Renders a listing of posts. Used inside a Divi Theme Builder archive template so the module reads from the current archive's query loop (category page, tag page, custom taxonomy archive, etc.).

The important attribute is useCurrentLoop — when set to on, Divi scopes the listing to whatever archive the visitor is currently viewing. Without it, the module falls back to Divi's default query (usually the most recent posts across the whole site) and every category archive shows the same list.

<!-- wp:divi/blog {"post":{"advanced":{"useCurrentLoop":{"desktop":{"value":"on"}}}},"builderVersion":"5.9.0"} /-->
The post namespace irregularity

Rows and columns organize their per-breakpoint attributes under a module namespace (module.advanced.columnStructure, module.advanced.type). Most other modules follow the same convention.

wp:divi/blog does not. Its useCurrentLoop attribute lives under a post namespace — not module, not blog, not any variant of the module slug. If you write it under module, the module renders but ignores the loop scope and every archive shows the same posts.

This convention is set by Divi's Visual Builder editor when you check the Use Current Loop box in the module's Content tab. There is no external documentation for it — it was found via the reverse-engineer-by-diff method (write a broken guess, open the post in the builder, check the box, save, read the post back, diff). The divi5_scaffold_block tool applies the correct namespace for you.

Using them in a Theme Builder template

Structural modules only make sense inside a template Divi assigns to specific archive or post-type conditions. To programmatically create a Divi Theme Builder template that uses wp:divi/blog, write the block markup into a new post of type et_theme_builder_layout (or the current Divi 5 Theme Builder post type on your install — use the reverse-engineer method to confirm), then attach it to your target archive condition via the Divi admin UI.

Why content is object-shaped

Every Divi 5 attribute is per-breakpoint. The design model assumes you might want a different value on desktop, tablet, and phone — so instead of a flat scalar, every attribute is a nested object keyed by breakpoint. A code module's content isn't "HTML HERE"; it's:

"content": {
    "innerContent": {
        "desktop": { "value": "<p>Hello world</p>" }
        // Optional:
        // "tablet":  { "value": "..." },
        // "phone":   { "value": "..." }
    }
}

Only desktop is required — if you don't specify tablet or phone, Divi inherits desktop for those breakpoints. When your content should be identical across all three, this is the entire attribute.

If you pass a flat string ("content": "<p>Hello</p>"), Divi's block parser doesn't error — it just doesn't recognize the shape and drops the value. The module renders empty.

The same shape applies to almost every attribute

Not just content. Colors, spacing, typography, border settings — everything on a Divi 5 module follows the {breakpoint: {value: ...}} pattern. When reverse-engineering an unknown attribute (see below), assume this shape by default.

The double-quote gotcha

A code module's content is embedded inside a JSON block-attribute payload. The block-comment syntax is:

<!-- wp:divi/code {"content":{"innerContent":{"desktop":{"value":"YOUR HTML HERE"}}}} /-->

A literal " inside YOUR HTML terminates the JSON string early. Divi sees the closing quote, tries to parse the rest as JSON structure, fails silently, and renders empty. Escape sequences (\", HTML entities, backslash tricks) that would work in most JSON contexts have all failed against Divi's block parser in real-world testing.

What actually works:

  • Use single quotes for HTML attributes. <a href='https://example.com' class='button'>Click</a> parses cleanly. This is the simplest fix — if your HTML attributes are always single-quoted, you never hit the issue.
  • HTML-entity-encode any needed double quotes. &quot; renders as " in the browser but stays as multi-character text through the JSON parse. Useful when you can't control quote style (imported content, third-party markup).

Special case: schema.org JSON-LD

JSON-LD requires double quotes per the JSON spec — you can't single-quote it and still have valid JSON. Which means JSON-LD literally cannot be embedded inside a Divi 5 Code module. Four escaping variants have been tried against real Divi 5.9.0 installs and all four failed silently.

The workaround is schema.org microdata. Microdata expresses the same structured data using HTML attributes (itemscope, itemtype, itemprop) instead of a JSON blob. Because it's entirely HTML attributes, it can be single-quoted:

<div itemscope itemtype='https://schema.org/Article'>
    <h1 itemprop='headline'>Article Title</h1>
    <div itemprop='author' itemscope itemtype='https://schema.org/Person'>
        <span itemprop='name'>Author Name</span>
    </div>
</div>

Google and other search engines index microdata identically to JSON-LD for SEO purposes. If you were reaching for JSON-LD to satisfy a structured-data requirement, microdata gets you the same result inside a Divi 5 module.

Reverse-engineer-by-diff — the method for any undocumented block

Elegant Themes doesn't publish a programmatic schema for every Divi 5 module. When you need a module type this doc doesn't cover, the reliable approach is to let Divi tell you the schema itself:

  1. Write a hand-authored guess programmatically. Use wp_create_post or wp_update_post to write your best-effort block markup into a test post. Save it. The post stores byte-for-byte and typically renders empty.
  2. Open the same post in the Divi 5 builder and add the same module by hand. Drop in the module type you're trying to write, configure it however you'd want programmatically, and save.
  3. Read the post back. Divi preserves your broken markup and appends its own correct version in the same document. Every difference between the two is legible in a single read — the correct attribute names, the correct nesting shape, the correct default values.
  4. Rebuild programmatically to match. Discard your original guess. Write the corrected shape into a fresh post. Confirm it renders.

This method works against any undocumented block Divi ships. The value isn't the specific schema you extract — it's that Divi's own storage format is the source of truth, and you can extract it in 5 minutes without waiting for documentation.

Do not drive Divi's code editor with simulated keystrokes

Browser automation that types HTML into Divi's Visual Builder code field triggers the editor's auto-close behavior — injecting stray </span>, </p>, and stray p> fragments that render on the frontend. The failure is silent and plausible-looking. Programmatic writes (wp_create_post, wp_update_post, or the scaffold tool below) don't hit this path — they set post_content directly.

The divi5_scaffold_block helper tool

Rather than assembling the 5-requirement wrapper by hand for every write, Royal MCP Pro ships a scaffold tool that emits the correct markup given content and module type. Two modes: read-only (returns markup) or write mode (atomically updates the target post + issues a 72-hour undo token).

divi5_scaffold_block

Emit ready-to-write Divi 5 block markup for a module. Collapses the 5-requirement wrapper into a single call. Detects the double-quote gotcha and warns. Optional write_to_post_id atomically writes into a post with a 72-hour undo token.

Arguments

NameTypeDescription
module_type requiredstringModule namespace slug. Shorthand (code, text, heading) or fully qualified (divi/code, divi/post-content, divi/blog).
contentstringModule content. Required for content-bearing types (code / text / heading). Rejected on structural modules (post-content, blog) and pass-through modules unless force:true.
builder_versionstringStamped on every block. Default 5.9.0. Wrong values trigger Divi's 5.0.0-public-alpha.18.2 fallback → empty render.
write_to_post_idintegerOptional. If provided, atomically wp_update_post + stamp _et_pb_use_builder + _et_pb_builder_version meta + issue 72h undo token. Omit to return markup only.
forcebooleanBypass the Divi-active check, the unknown-module check, and the content-refusal check. Advanced use only — result may render blank if the module / version / schema combination isn't recognized. Default false.

Read-only example

Scaffold a Divi 5 code module with content "<p>Hello world</p>" using divi5_scaffold_block.

Then use the returned block_markup with wp_create_post to publish a new page.

Write example

Scaffold a Divi 5 code module with my article HTML and write it directly into post 42 using divi5_scaffold_block with write_to_post_id=42.

The post now has a valid D5 layout wrapping my content. If anything looks wrong, undo the 72h token.

Response fields

FieldDescription
block_markupThe wrapped Divi 5 markup, ready to write into post_content.
module_typeEchoes the fully-qualified module type (e.g. divi/code) — shorthand inputs are normalized here.
builder_versionEchoes the stamped version (default 5.9.0).
byte_lengthByte length of block_markup. Useful for storage-limit sanity checks on large payloads.
requirements_appliedMap of the 5 requirements with a per-item explanation of what the tool did.
warningsArray of soft-warning strings. Currently detects literal double quotes in content.
writtentrue in write mode + post_id populated. false in read-only mode.
post_idWrite mode only — the target post ID the markup was written into.

Undo (write mode only): 72h token — restores prior post_content exactly.

Manual authoring (when the scaffold isn't enough)

The scaffold first-class-supports content-bearing modules (code, text, heading) and structural modules (post-content, blog). Other module types — image, button, gallery, divider, audio, video, cta, blurb, testimonial, social-follow, and the rest of Divi's built-in library — are recognized but refuse content unless you pass force:true. When you need a module type the scaffold doesn't natively handle with content, use the schema from the verified schema section plus the reverse-engineer method to derive the specific attribute shape, then write via wp_create_post or wp_update_post directly.

The core pattern is identical:

Create a new draft page titled "Manual Scaffold Test" with the following post_content using wp_create_post:

<!-- wp:divi/placeholder --><!-- wp:divi/section {"builderVersion":"5.9.0"} -->
<!-- wp:divi/row {"module":{"advanced":{"columnStructure":{"desktop":{"value":"4_4"}},
"flexColumnStructure":{"desktop":{"value":"equal-columns_1"}}},"decoration":{"layout":
{"desktop":{"value":{"flexWrap":"nowrap"}}}}},"builderVersion":"5.9.0"} -->
<!-- wp:divi/column {"module":{"advanced":{"type":{"desktop":{"value":"4_4"}}},"decoration":
{"sizing":{"desktop":{"value":{"flexType":"24_24"}}}}},"builderVersion":"5.9.0"} -->
<!-- wp:divi/YOUR_MODULE {"YOUR_ATTRS":{...},"builderVersion":"5.9.0"} /-->
<!-- /wp:divi/column -->
<!-- /wp:divi/row -->
<!-- /wp:divi/section --><!-- /wp:divi/placeholder -->

The outer wrapper (placeholder / section / row / column) is invariant — byte-for-byte the same as the verified schema above, including the row's decoration.layout block. Only the innermost module line changes per module type.

Still Stuck? Two-Step Support Path

If your Divi 5 module renders empty even after following the schema, work through these two steps in order.

Step 1: Verify each of the 5 requirements independently

  1. Read the post's post_content back through wp_get_post. Confirm the placeholder wrapper is present as the outermost tag.
  2. Confirm builderVersion appears on every block (section, row, column, module) — grep the post content for builderVersion; you should see it four times per module.
  3. Confirm content-bearing modules use the {innerContent: {desktop: {value: "..."}}} shape rather than a flat string.
  4. Confirm the row has columnStructure and flexColumnStructure; the column has type and flexType.
  5. Grep the content for literal " characters inside HTML attributes. If any appear, switch to single quotes or &quot;.

Most silent-render bugs resolve at one of these five checks.

Step 2: Email priority support

If the module still renders empty after all five checks pass, email priority support from your purchase email address at support@royalplugins.com. Priority email support is included with your license — typical response within 24 hours. Never include your license key in email; we look it up from your purchase address.

What to include in your email
  • Your hosting provider (SiteGround, Cloudways, Hostinger, Kinsta, WP Engine, Liquid Web, self-hosted VPS, etc.)
  • Divi version (from Appearance → Themes or your Divi Builder plugin version)
  • Royal MCP Pro version from WP Admin → Plugins
  • Which MCP client you used (claude.ai custom connector, Claude Desktop, Cursor, etc.)
  • The exact module_type and content you tried to author (or the full block markup if authored manually)
  • The current post_content of the target post (read it back via wp_get_post and paste)
  • A screenshot of what the frontend actually renders compared to what you expected
  • Whether the module renders correctly when configured by hand in Divi's Visual Builder on the same post