Create editable Elementor product pages and long-form blog articles on
WordPress programmatically over REST or an MCP connection using an
Application Password. Feed it JSON; it writes valid _elementor_data, verifies
that it persisted, and validates blog table-of-contents links before writing.
Built as a reusable agent skill (SKILL.md), with standalone Node.js scripts and
no runtime dependencies.
Elementor doesn't store its layout in post_content; it stores a JSON widget
tree in the _elementor_data post-meta. Once valid JSON can be written there,
the same workflow can generate product pages and widget-native blog articles.
SKILL.md Claude skill entry point + full workflow
README.md this file
.env.example credential template (copy to .env)
package.json npm scripts (no runtime deps)
scripts/
lib/wp-client.js REST auth + create/update/get/delete/probe
lib/elementor.js widget builder + structure/TOC validator
lib/layout.js reusable 15-section product layout
lib/blog-layout.js editable blog layout with linked TOC anchors
check-endpoint.js verify auth + endpoints + meta writability
validate.js structural validation (no network)
build-pages.js build + write pages (supports --dry-run)
data/
example-product.json product-page config schema
example-blog.json blog config schema (_layout: "blog")
references/
mcp-setup.md MCP client config + auth modes
elementor-data-format.md the _elementor_data JSON structure
wireframe-15-section.md the 15-section layout spec
blog-template.md blog sections, widgets, TOC contract, examples
troubleshooting.md every error we hit + the fix
- Node.js 18+ (uses built-in
fetch; nonpm installneeded). - A WordPress site with Elementor installed and the Elementor meta keys
writable over REST (verify with
npm run check). - A WordPress Application Password (Users → Profile → Application Passwords).
cp .env.example .env # fill in WP_SITE / WP_USER / WP_APP_PASSWORD
# validate the example layout offline (no site needed):
node scripts/validate.js
# with your .env loaded, verify the connection:
node --env-file=.env scripts/check-endpoint.js
# dry-run either layout (writes JSON to tmp/, no site changes):
npm run build:product
npm run build:blog
# real build (creates a DRAFT page):
node --env-file=.env scripts/build-pages.js data/example-product.jsonSet "_layout": "blog" and define article sections in sections. Any section
with both tocLabel and anchor is added to the Contents list. The generated
page uses Elementor's core icon-list widget for links and a core menu-anchor
widget for each destination, so both remain editable in Elementor.
{
"type": "prose",
"anchor": "planning-basics",
"tocLabel": "Planning basics",
"heading": "Planning basics",
"paragraphs": ["Article copy goes here."]
}scripts/validate.js checks that every #fragment link has exactly one valid
Menu Anchor target. See references/blog-template.md for the supported article
sections and widget map.
--env-fileis built into Node 18.6+. On older Node, export the vars yourself or use a loader likedotenv.
Then open the new draft in Elementor → Update (or Elementor → Tools → Regenerate CSS & Data) so the render cache picks up the layout.
node --env-file=.env scripts/build-pages.js data/p1.json data/p2.json data/p3.jsonOverwrite an existing page instead of creating a new one:
node --env-file=.env scripts/build-pages.js data/p1.json --update 12345Publish immediately (default is draft):
node --env-file=.env scripts/build-pages.js data/p1.json --publish- Two-step write — create the page, then write
_elementor_data. Big meta in the create call → HTTP 500. elementsis always an array of objects — never a raw string, or Elementor'sadd_child()throws.validate.jscatches this offline.- TOC links require Menu Anchor targets — validation rejects broken or duplicate article anchors before the site is touched.
- Elementor caches the render — after an API write, Update once in the editor to see it.
- App Password ≠ JWT — streamable MCP endpoint is JWT-only; REST + the non-streamable endpoint take the App Password.
See references/troubleshooting.md for the full list.
- Credentials live only in
.env(gitignored). Scripts readprocess.env. - Pages are created as draft by default.
- If an Application Password is ever exposed, rotate it in Users → Profile → Application Passwords.
MIT — see LICENSE.