Skip to content

WordPress pages — Gutenberg & Elementor

WordPress pages diverge from blog posts in two ways: they support multiple page builders (Gutenberg + Elementor on the same site), and one of those builders (Elementor) stores its layout as JSON in postmeta rather than as HTML in the body. The worker hides most of that complexity behind a single pair of tools — create_page_draft / update_page_draft — but you need to know how dispatch works.

ToolBuilderWhat it does
list_builders_for_siteanyAsks the worker which builders are enabled
create_page_draftGutenberg or ElementorNew draft page
update_page_draftGutenberg or ElementorPatch a draft
schedule_pageanySchedule a draft for future publish (≥60s lead)
list_page_templatesElementor onlyReturns templates + their {{PLACEHOLDERS}}
diagnose_siteanyConfirms WP REST + Elementor + the functions.php snippet

For raw HTML/CSS/JS pages (no page builder), see Code-first pages — those use create_page_from_code instead.

A WordPress site can have one or both builders enabled, configured at /admin/sites → edit → Page builders checkboxes. The schema:

sites.available_builders // JSON, e.g. ["gutenberg"] or ["gutenberg","elementor"]
sites.default_builder // e.g. "gutenberg"

Three states:

available_buildersWhat create_page_draft does without page_builder arg
["gutenberg"]Uses Gutenberg — no arg required.
["elementor"]Uses Elementor — no arg required.
["gutenberg", "elementor"]Refuses with E_BUILDER_REQUIRED. Caller must pass page_builder: "gutenberg" | "elementor".

Pass page_builder explicitly when you know which builder you want. The override must be in available_builders — otherwise the worker throws E_BUILDER_NOT_AVAILABLE.

If you’re not sure which builder is enabled, get Claude to look first:

List the page builders available on blog-acme.

Claude calls list_builders_for_site and gets back something like:

{ "available": ["gutenberg", "elementor"], "default": "gutenberg", "multiple": true }

When multiple: true, a good prompt is:

The site has both Gutenberg and Elementor. Which one should I use?

Claude will ask you in chat. Reply with one, then let it call create_page_draft with the explicit page_builder arg.

Simplest case — same shape as blog posts, just on /wp/v2/pages:

Create a draft page on blog-acme titled “Mobile detailing in Boston”, intro 2 paragraphs about local service, then a 3-item list of what’s included.

Claude calls create_page_draft with site_id, title, and content_md/content_html. The worker:

  1. Resolves the builder (Gutenberg either implied or explicit).
  2. Posts to /wp/v2/pages?status=draft.
  3. Returns the page ID + admin edit URL.

Updates work the same:

On post 89 of blog-acme, change the H1 to “Auto detailing in Boston” and add a price section at the bottom.

→ Claude calls update_page_draft with page_id: "89" and a patch.

Elementor stores its entire layout as a JSON blob in postmeta (_elementor_data). Hand-editing that JSON is brittle, so the worker uses a template duplicate pattern instead:

  1. You build a polished page in wp-admin → Elementor with {{UPPER_SNAKE}} placeholders sprinkled in headings, button labels, link URLs, image alts, etc.
  2. You leave it as a draft or private so it’s not visible publicly.
  3. Claude calls list_page_templates to learn the templates + their placeholders.
  4. Claude calls create_page_draft with elementor_template_id + a variables: { HERO_H1: "...", CTA_TEXT: "...", ... } map. The worker clones the JSON, substring-replaces each {{KEY}} with the variable value, and writes a new page.

See Elementor templates for the placeholder convention and recommended template set. The short version:

{{BIZ_NAME}}, {{CITY}}, {{PHONE}}, {{HERO_H1}}, {{HERO_SUB}},
{{CTA_TEXT}}, {{CTA_LINK}}, {{IMG_HERO}}, {{TESTIMONIAL_1}}, …

Assume you’ve built a “Service landing” template at page ID 42, with {{BIZ_NAME}}, {{CITY}}, {{HERO_H1}}, {{CTA_TEXT}} placeholders:

Create a city landing page on acme-detail from Elementor template 42, variables: BIZ_NAME=“Acme Detailing”, CITY=“Cambridge”, HERO_H1=“Mobile Ceramic Coating in Cambridge”, CTA_TEXT=“Book Now”.

Claude calls:

{
"tool": "create_page_draft",
"args": {
"site_id": "acme-detail",
"title": "Mobile Ceramic Coating in Cambridge",
"page_builder": "elementor",
"elementor_template_id": "42",
"variables": {
"BIZ_NAME": "Acme Detailing",
"CITY": "Cambridge",
"HERO_H1": "Mobile Ceramic Coating in Cambridge",
"CTA_TEXT": "Book Now"
}
}
}

The worker checks every {{KEY}} referenced in the template has a value in variables — missing keys throw E_MISSING_PLACEHOLDER with the list of missing names. Extra keys are silently ignored.

Re-clone page 91 from template 42, swap CITY=“Allston”.

Claude calls update_page_draft with elementor_template_id + the new variables. The worker re-reads the template, runs the substitution, and overwrites _elementor_data on page 91. The page ID + slug stay the same — no SEO disruption.

If you call update_page_draft without elementor_template_id, the worker treats it as a simple WP-REST patch (title, slug, parent, menu_order). The Elementor JSON is untouched. Useful for tweaking metadata without re-cloning.

Schedule page 91 on acme-detail for next Monday at 8am Boston time.

Claude calls schedule_page — WP only; Duda has no per-page time schedule (use update_page_draft { publish_on_next_site_publish: true } over there). Same 60-second minimum-lead rule as blog posts.

Run this once per site before you trust Elementor:

Diagnose acme-detail.

Claude calls diagnose_site. The response has five checks:

{
"wordpress": { "ok": true, "user": "automation" },
"elementor": { "active": true, "version": "3.21.0" },
"functions_snippet": { "applied": true },
"mcp_adapter": { "installed": false },
"elementor_mcp": { "installed": false },
"code_page_plugin": { "installed": true }
}
  • functions_snippet: applied=false — you haven’t registered the meta keys yet. See docs/setup.md for the snippet.
  • mcp_adapter + elementor_mcp — only relevant if you flip mcp_backend to wp-mcp-adapter for widget-level editing. Optional.
  • code_page_plugin — required for code-first pages, unrelated to Gutenberg/Elementor.
SymptomWhat’s happeningFix
E_BUILDER_REQUIREDSite has multiple builders and Claude didn’t specifyPass page_builder: "gutenberg" or "elementor".
E_BUILDER_NOT_AVAILABLEPassed a builder not in available_builders/admin/sites → edit → tick the builder.
E_MISSING_PLACEHOLDERVariables map missing a key the template usesAdd the missing key to variables.
E_UPSTREAM — WP 400 _elementor_data not exposedfunctions.php snippet not appliedApply the snippet; verify with diagnose_site.
E_BAD_ELEMENTOR_DATAThe template’s stored JSON failed to parseOpen the template in wp-admin, re-save it via Elementor (rewrites the JSON cleanly).
E_FORBIDDEN_OP on schedule_pageTime within 60s of now or in the pastPick a later time.