Workflow IA

Figma to Elementor: there's no button, there's a method

Two MCPs to read Figma, two to write into Elementor, WP-CLI as a fallback, a browser to measure to the pixel. The method behind a delivered campaign, and the next site.

Goal: go from a Figma mockup to a pixel-accurate Elementor page, without a converter. Two MCPs to read the mockup, two to write into Elementor, WP-CLI when neither is enough, and a windowless browser to measure the gap. Target tolerance: 4 pixels.

"Figma to Elementor": the search keeps coming back, and the expected answer is a button. Import, convert, publish. I don't use any. My latest campaign, a thirteen-section page 11,109 pixels tall, went from Figma to the site between August 28 and September 15, 2026, with Claude Code and a handful of MCPs. Here is the method, the tools, and the question to ask before the first line of code.

The question that decides everything: who will edit the page?

Before opening Figma, I ask the project one question. Not "how do I reproduce this mockup?". Rather: "who will touch this page after me, and to do what?" Two possible answers, two architectures.

CriterionNative Elementor widgetsPHP template + ACF inside an Elementor shell
Who editsAn editor who composes their own pagesSomeone who changes text and images
FreedomInsert, duplicate, reorder blocksFill in fields, the layout never moves
Pixel accuracyGood, bounded by Elementor's controlsTotal: the CSS is hand-written
Repeated listsWidgets repeated one by oneACF repeater, one PHP loop
Writing toolEMCP ToolsPHP, ACF, WP-CLI
My caseThe magazine site (mockup pending)The awareness campaign

The campaign was an awareness page for a public health association. A long page, fixed content, and on the client side one person who changes text and images, never the structure. The PHP template was the obvious fit. A technical argument settled it: Elementor's Dynamic Tags cannot loop over an ACF repeater. And the mockup was full of them: lists, cards, testimonials.

The magazine site, on the other hand, will be composed by editors, without a line of code. They insert blocks, duplicate them, reorder them. There, only native widgets hold up. Same Figma in, opposite output.

Reading the mockup: two Figma MCPs, two roles

Claude Code can't see Figma. It needs a bridge. I use two, and neither replaces the other.

CriterionOfficial Figma MCPclaude-talk-to-figma plugin
ConnectionRemote API, statelessWebSocket to Figma Desktop
ParallelismSeveral calls at onceOne shared channel, one call at a time
StrengthWeights per text segment, source imagesExact colors, coordinates, full text inventory
Tools usedget_design_context, get_metadata, download_assets, get_screenshotget_node_info, get_styled_text_segments, scan_text_nodes, export_node_as_image

The official connector: structure and images

get_design_context returns React with Tailwind classes. For a WordPress theme, that code is useless as is: I only keep the structure, the texts, the colors and the spacing. Its real value lies elsewhere. It gives the weight of every text segment. On one paragraph, it showed at once that two proper names were Bold and everything else SemiBold, where the plugin would have required a full survey.

download_assets delivers the source images, not just the render: a 3,452 x 1,786 pixel file where the node export is 812 x 457. Figma's crop can be rebuilt from the original. A detail that matters: the URLs expire within minutes. Download right away.

The plugin: exact measurements

get_node_info gives a node's colors, absolute coordinates and exact characters. scan_text_nodes is the only way to find a text when you don't know where it lives: 87 texts on the French frame, a 58 KB result. I have a script read it, not the model. The plugin's only flaw: one shared WebSocket channel, one call at a time, frequent timeouts. When it stalls, I fall back on the official connector.

Three reading traps

  • IDs change. Three sets of IDs since the start of the project, one per re-paste of the mockup. I find nodes by name or by text, never by an ID written down the week before.
  • Big results overflow. get_metadata on the whole page weighs 311,000 characters. The result goes to a file, a Python script extracts the inventory, and I only call get_design_context on the nodes that matter.
  • A text's box is not the element's box. A list item measured 448.16 px in Figma: the width of the text alone. With the bullet indent, the right value was 475.4. One word wrapped, and a single pixel row gave it away.

Building: one section at a time, no design initiative

One rule opens every brief: no design initiative. Everything comes from Figma. Whatever is missing, a photo, a link, an absent text, becomes clearly flagged placeholder content and an open question. Never a text that could pass for final.

On the WordPress side, the Elementor page is just a shell. It holds a single widget, the [prevention_page] shortcode, which assembles the sections in a fixed order. ACF stores the content, and each section has its own Show/Hide toggle.

Each section then follows the same loop:

Figma (1440 px frame)
   │  official MCP : structure, weights, source images
   │  plugin       : colors, coordinates, texts
   â–Ľ
Section plan (.md)               measurements, traps, open questions
   â–Ľ
section-*.php + ACF fields + CSS block
   â–Ľ
Idempotent content script        wp eval-file script.php <post_id>
   â–Ľ
Headless capture at 1440 px  →   pixel rows against the Figma render
   â–Ľ
Gap ≤ 4 px?       no: fix           yes: commit

Claude Code wrote the PHP, the CSS and the scripts. The trade-offs, the plan reviews and every visual sign-off stayed mine. The content script is idempotent: run it ten times, get the same result ten times, and it flags any content that is still a placeholder.

Measure, don't eyeball

The eye lies. A title set in Anton renders in a 74.4 px box where Figma counts 79. Aligning the boxes gives the wrong answer. Aligning the ink gives the right one.

So I compare the Figma render and a capture of the site at 1440 px with the same Python script (PIL, numpy): a color mask, contiguous pixel rows grouped together, and you get the top of every line and the extent of every box. Tolerance: 4 px on lines and boxes, 4 px on section height. Any larger gap is logged with its cause.

The capture comes from a windowless Chrome, driven through the DevTools protocol. One constraint showed up along the way: comparing pixels requires the same browser. A baseline taken in one Chrome version against captures from another differed everywhere, because of font rendering.

Living with Elementor without letting it take over

A PHP template inside an Elementor site still inherits from Elementor. Three traps, all of them hit:

  • The kit wins the conflicts. The global kit forces font and color on every heading with a 0-1-1 specificity selector. A plain class rule (0-1-0) loses. The fix: prefix with the page's root container (0-2-0). No !important.
  • The old CSS stays loaded. Elementor loads elementor-post-<ID>.css based on the page ID, not on how the page renders: 46 declarations from an old layout were still active. A targeted wp_dequeue_style removes them without touching the header or footer.
  • Fonts don't follow. Elementor serves Google Fonts locally, page by page. A PHP template no longer benefits from that: the theme self-hosts its own.

Writing into Elementor: which MCP?

On the magazine site, native widgets are the way, and writing goes through an MCP. I compared what exists today by querying each server, not by reading its landing page. Measured on September 25, 2026, on Elementor 4.2.0 and Elementor Pro 4.1.2.

CriterionEMCP ToolsOfficial Elementor MCPMCP AdapterWP-CLI
NatureThird-party pluginModule built into ElementorWordPress.org pluginCommand line
Tools exposed1375None of its ownEverything, no guardrails
Classic widgetsYesNo, atomic elements onlyNot applicableYes, as raw JSON
ActivationInstall the pluginHidden experimental settingInstall the pluginAlready there
My useMain toolWatch listFoundation for the first twoFallback

EMCP Tools: the workhorse

137 active tools in version 3.17.1: classic and atomic widgets, containers, global classes, variables, templates, ACF fields, media, history with rollback. The 34 risky tools (file writes, SQL writes, plugins, users) are disabled by default. You enable them one at a time, never all at once.

I was still on 3.0.0. Updating to 3.17.1 wasn't a nicety: in between came fixes for data loss. update-page-settings replaced the whole settings object, so a one-key tweak on the kit wiped custom colors and typography. Third-party widgets silently vanished on save. Plugin and database backed up before, checked after: 51 Elementor pages, zero invalid JSON.

A habit worth keeping: re-read after every creation. During validation, add-container accepted css_classes without error and never saved it. get-element-settings shows what is actually stored.

The official MCP: five tools, for now

Elementor's announcement promises full pages, the design system, theme parts, custom widgets. In Elementor 4.2.0's code, the module already exists, behind a hidden experimental setting that is off by default:

wp option update elementor_experiment-e_wp_abilities_api active

Once enabled, it exposes five tools: list-pages, get-page-structure, update-page-settings, create-page, get-globals. It only targets V4 atomic elements. The magazine site has 51 Elementor pages, and only 11 contain atomic elements. The announced capabilities also require Angie, Elementor's agent, which I haven't installed. I keep it enabled to follow how it evolves. I explained elsewhere why V4 isn't my ground yet.

The MCP Adapter: why both coexist

EMCP and Elementor's module both register with the same WordPress MCP Adapter. Each gets its own route, no conflict: 137 and 5 tools answer side by side. One condition: EMCP 3.17.1 or later, which fixes an OAuth discovery bug where it answered in place of a second MCP plugin.

{
  "mcpServers": {
    "emcp-tools": {
      "type": "http",
      "url": "http://site.test/wp-json/mcp/emcp-tools-server",
      "headers": { "Authorization": "Basic <base64(user:application-password)>" }
    },
    "elementor-official": {
      "type": "http",
      "url": "http://site.test/wp-json/elementor/mcp",
      "headers": { "Authorization": "Basic <base64(user:application-password)>" }
    }
  }
}

WP-CLI: the fallback

Without an MCP, you write the _elementor_data JSON directly, the way Elementor does it. That's how I changed the mobile header on the campaign's site:

update_metadata('post', $id, '_elementor_data', wp_slash(wp_json_encode($data)));
delete_post_meta($id, '_elementor_css');
delete_post_meta($id, '_elementor_element_cache');
\Elementor\Plugin::$instance->files_manager->clear_cache();

It works, with no validation at all. Four rules learned on sites without an MCP:

  • Back up with mysql -N -r - without -r, the client doubles the backslashes and the JSON becomes invalid.
  • _elementor_page_settings is serialized PHP - read it as hex, then update_post_meta(), or it gets corrupted.
  • A page created by script needs four metas - _elementor_edit_mode, _elementor_template_type, _elementor_version and the page template. Without them, the page renders empty, with no error.
  • URLs are stored escaped - a search-replace must also look for https:\/\/.

The magazine site: the method, before the mockup

The magazine site's mockup hasn't arrived yet. The site runs on a provisional design system: Elementor global colors and typography, CSS variables. Here is the order I will follow, taken from the campaign and adapted to native widgets:

  1. Inventory the mockup with get_metadata, parsed by script, before any expensive call.
  2. Carry colors and typography into Elementor's global settings, never widget by widget.
  3. Build the blocks one by one on a workshop page, through EMCP.
  4. Re-read every creation with get-element-settings.
  5. Measure at 1440 px against the Figma render, same script, same tolerance.
  6. Sign off each block myself before turning it into a reusable template with save-as-template.

The difference from the campaign fits in one line: the end result is no longer a page, it's a library of blocks that editors assemble without me.

Results

MetricValue
Campaign buildAugust 28 to September 15, 2026
Commits over the period200
Mockup1,440 x 11,109 px, 13 sections
Texts inventoried87 in French, 96 in Dutch
PHP section files (full template)21
Template CSS4,913 lines
Assets exported from Figma28
Measurement tolerance4 px
EMCP Tools 3.17.1 tools137
Official MCP tools (Elementor 4.2.0)5

Limits

  • The PHP template locks the client in. They change text, not structure. A new section means code. A deliberate choice, not a hidden flaw.
  • The official MCP is young. Five tools, atomic elements only, behind a hidden setting. Worth re-checking with every release.
  • EMCP is a third-party plugin. Seventeen minor releases since 3.0.0. Every update calls for a backup and a read of the changelog.
  • Measuring doesn't judge taste. A zero gap says the page is faithful, not that it's good. Visual sign-off stays human.

The takeaway

  • The first question isn't technical. Who will edit the page decides the architecture, before Figma is even open.
  • Reading and writing are two jobs. Two MCPs to read the mockup, one MCP to write into Elementor, WP-CLI when neither is enough.
  • Query the server, not the landing page. 137 tools on one side, 5 on the other: no announcement said so.
  • Measuring beats eyeballing. Pixel rows, a tolerance, and every gap explained.
  • It's not a magic button. It's a method, tools and the discipline to check. The magazine site will start from there.

Resources

The two MCP servers for Elementor, the WordPress foundation that lets them coexist, and the Figma plugin used for exact measurements.

EMCP Tools · MCP Adapter · Elementor MCP · claude-talk-to-figma

The full tutorial, on request

This article sums up the method. I can turn it into a full step-by-step tutorial: the pixel measurement script, the section plan template, the Figma and Elementor MCP setup, and every trap logged along the way. Moving from Figma to Elementor and want that tutorial, or a hand with your project? Write to me.

Get in touch