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.
Tools you’ll use
Section titled “Tools you’ll use”| Tool | Builder | What it does |
|---|---|---|
list_builders_for_site | any | Asks the worker which builders are enabled |
create_page_draft | Gutenberg or Elementor | New draft page |
update_page_draft | Gutenberg or Elementor | Patch a draft |
schedule_page | any | Schedule a draft for future publish (≥60s lead) |
list_page_templates | Elementor only | Returns templates + their {{PLACEHOLDERS}} |
diagnose_site | any | Confirms 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.
Multi-builder model
Section titled “Multi-builder model”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_builders | What 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.
Asking Claude to pick
Section titled “Asking Claude to pick”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.
Gutenberg flow
Section titled “Gutenberg flow”Simplest case — same shape as blog posts, just on /wp/v2/pages:
Create a draft page on
blog-acmetitled “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:
- Resolves the builder (Gutenberg either implied or explicit).
- Posts to
/wp/v2/pages?status=draft. - 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 flow — duplicate-and-fill
Section titled “Elementor flow — duplicate-and-fill”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:
- You build a polished page in wp-admin → Elementor with
{{UPPER_SNAKE}}placeholders sprinkled in headings, button labels, link URLs, image alts, etc. - You leave it as a
draftorprivateso it’s not visible publicly. - Claude calls
list_page_templatesto learn the templates + their placeholders. - Claude calls
create_page_draftwithelementor_template_id+ avariables: { 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}}, …Example prompt
Section titled “Example prompt”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-detailfrom 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.
Updating an Elementor page
Section titled “Updating an Elementor page”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.
Scheduling pages
Section titled “Scheduling pages”Schedule page 91 on
acme-detailfor 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.
Pre-flight: diagnose_site
Section titled “Pre-flight: diagnose_site”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. Seedocs/setup.mdfor the snippet.mcp_adapter+elementor_mcp— only relevant if you flipmcp_backendtowp-mcp-adapterfor widget-level editing. Optional.code_page_plugin— required for code-first pages, unrelated to Gutenberg/Elementor.
Common errors
Section titled “Common errors”| Symptom | What’s happening | Fix |
|---|---|---|
E_BUILDER_REQUIRED | Site has multiple builders and Claude didn’t specify | Pass page_builder: "gutenberg" or "elementor". |
E_BUILDER_NOT_AVAILABLE | Passed a builder not in available_builders | /admin/sites → edit → tick the builder. |
E_MISSING_PLACEHOLDER | Variables map missing a key the template uses | Add the missing key to variables. |
E_UPSTREAM — WP 400 _elementor_data not exposed | functions.php snippet not applied | Apply the snippet; verify with diagnose_site. |
E_BAD_ELEMENTOR_DATA | The template’s stored JSON failed to parse | Open the template in wp-admin, re-save it via Elementor (rewrites the JSON cleanly). |
E_FORBIDDEN_OP on schedule_page | Time within 60s of now or in the past | Pick a later time. |
What’s next
Section titled “What’s next”- Elementor templates — recipes — placeholder catalog and per-vertical layout suggestions.
- Code-first pages — when you’d rather generate HTML/CSS/JS than fight a page builder.
- Content + Design teams — running writing and deploying as separate roles via GitHub markdown.