# Figma to Elementor Converter Widget

> Beta widget that imports Figma design exports and converts them into Elementor sections. Pairs with `FigmaImageHandler` admin AJAX endpoint that downloads Figma-exported images (served from `storage.googleapis.com`) into the WP media library.

**Class file:** `includes/Elements/Figma_To_Elementor.php` (161 lines)
**Slug:** `figma-to-elementor` (widget id `eael-figma-to-elementor`)
**Public docs:** <https://essential-addons.com/docs/ea-figma-to-elementor-converter/>
**Pro-shared:** Pro-only. **External integration: Figma CDN (via `storage.googleapis.com`)**. **Beta**.

## Overview

A bridge widget for the Figma → Elementor workflow. Users paste a Figma frame export, and the widget triggers a server-side AJAX flow that downloads referenced images from `storage.googleapis.com` (where Figma serves exports), saves them to the WP media library, and returns attachment IDs. The widget class itself is thin (161 lines) — most logic lives in `FigmaImageHandler` (`includes/Classes/FigmaImageHandler.php`), registered via `essential_adons_elementor.php` `admin_init` hook.

The "beta" badge is hardcoded into `get_title()` via inline SVG (`Figma_To_Elementor.php:21-23`).

## Pro vs Lite

Pro-only.

## File Map

| File | Role |
| --- | --- |
| `includes/Elements/Figma_To_Elementor.php` | Widget shell — controls + render (thin) |
| `includes/Classes/FigmaImageHandler.php` | AJAX handler `wp_ajax_eael_upload_figma_images` — downloads + media-library inserts |
| `essential_adons_elementor.php` (`admin_init` hook, line 130+) | Registers `FigmaImageHandler` |
| `config.php` entry `'figma-to-elementor'` | Single edit JS dep (no front-end CSS) |

## Architecture

- **Two-class split** — widget class only handles editor UI / panel; image fetching is a separate admin-AJAX handler. Lets the heavy network work run in admin context with proper capability checks rather than at widget render.
- **Host allowlist for SSRF** — `FigmaImageHandler::validate_figma_url` (line 74) checks the URL host against an allowlist (`storage.googleapis.com`). HTTPS-only scheme enforced. This is the **correct SSRF pattern** — other extensions (custom-cursor SVG, this one) need similar validation.
- **AJAX auth triad** — nonce (`essential-addons-elementor`), `edit_posts` cap, `upload_files` cap (all three required). Strictest of any Pro AJAX endpoint.
- **`is_dynamic_content() = true`** — line 50: returns true. Tells Elementor this widget produces dynamic content; affects caching behaviour.
- **Beta SVG inlined in `get_title()`** — `Figma_To_Elementor.php:18` returns an inline SVG with the "Beta" badge wordmark concatenated after the localized title string. Editor-only — never reaches frontend.
- **No CSS source** — the widget relies on Elementor's converted output, not on its own styling.
- **No frontend JS** — widget is editor-only convert UI; converted output is plain Elementor sections.

## Render Output

```html
<div class="elementor-widget-container">
  {converted Elementor content — depends on user's Figma import}
</div>
```

The widget acts as a converter — once the user runs the import, the saved page contains the **converted output** (sections, images, text widgets), not the Figma-To-Elementor widget itself. Effectively a one-shot transformer.

## Controls Reference

| Control id | Tab → Section | Type | Purpose |
| --- | --- | --- | --- |
| Figma file URL / API key | Content → section_figma_to_elementor | TEXT | User-supplied Figma reference |
| Convert button | Content → section_figma_to_elementor | (custom UI in edit JS) | Trigger the import flow |

(Most of the widget UI is built in `src/js/edit/` rather than via Elementor controls — verify in the JS source.)

## Conditional Dependencies

N/A — the widget has minimal native controls; configuration happens through the editor's custom UI in edit JS.

## JavaScript Lifecycle

**Edit JS only** — no frontend JS. The edit-mode script handles:

1. User pastes Figma URL / API key
2. Edit JS calls `wp_ajax_eael_upload_figma_images` with the URL list
3. Server downloads images, returns attachment IDs
4. Edit JS inserts converted Elementor sections into the document

## Hooks & Filters

### AJAX endpoints

| Action | Handler | Auth |
| --- | --- | --- |
| `wp_ajax_eael_upload_figma_images` | `FigmaImageHandler::eael_handle_figma_images_upload` | logged-in `edit_posts` + `upload_files` + nonce |

**No `nopriv` variant** — Figma import requires authenticated admin / editor.

### Elementor hooks consumed

Standard widget hooks. Plus `admin_init` (Pro entry file) for FigmaImageHandler registration.

## Common Issues

| Symptom | Likely cause | Diagnose | Fix |
| --- | --- | --- | --- |
| "Invalid nonce" on import | Stale nonce after long edit session | Re-login | Refresh the editor page; nonces age out |
| "Insufficient permissions" | User lacks `upload_files` cap | Check user role | Grant cap or assign higher role |
| Image download fails silently | Figma URL not allowlisted (not `storage.googleapis.com`) | Inspect URL — must be HTTPS + match allowlist suffix | Verify the user pasted a real Figma export URL, not a Figma-app link |
| Converted layout looks wrong | Conversion logic limitation | — | Beta — feedback channel to wpdeveloper.com |

## Known Limitations

- **Beta** — name and badge in `get_title()` reflect this; feature behaviour subject to change
- **`storage.googleapis.com` is the only allowlisted host** — if Figma's CDN changes hosts, imports break until allowlist updated
- **No timeout on `wp_remote_get`** in `FigmaImageHandler:119` — flagged in [`docs/architecture/external-api-integrations.md`](../architecture/external-api-integrations.md). Default 5s timeout.
- **`actual_mime` check after download** — file is fully downloaded before MIME is validated. Saves a bandwidth-burning attack vector ONLY if the file size is also checked (verify).
- **Image attachments are added to media library** — fills media-library with imported assets; no cleanup if conversion is undone
- **`is_dynamic_content() = true`** — verify this is intentional; once converted, the output is static
- **No batching / rate-limiting** for multi-image imports — a Figma file with 200 images triggers 200 sequential `wp_remote_get` calls, all in one AJAX request

## Cross-References

- Architecture: [`docs/architecture/external-api-integrations.md`](../architecture/external-api-integrations.md) — Figma allowlist pattern
- Shared patterns: [`_patterns.md`](_patterns.md)
- Skill: [`/external-api-audit`](../../.claude/skills/external-api-audit/SKILL.md)
