# Figma Plugin Integration Guide Connect your Figma plugin to the GIF Encoder API to export selected frames as PNGs and receive animated GIFs back. --- ## Architecture Figma plugins run in two separate threads: | Thread | Has access to | Limitation | |---|---|---| | **Main thread** (`code.js`) | Figma scene, `exportAsync`, `figma.*` | No `FormData`, no `Blob`, limited `fetch` | | **UI iframe** (`ui.html`) | Full browser APIs: `fetch`, `FormData`, `Blob` | No Figma scene access | The workflow: export PNGs in the main thread, send the bytes to the UI iframe via `postMessage`, then upload to the API from the iframe. ``` Figma Main Thread UI Iframe GIF Encoder API ───────────────── ───────── ─────────────── exportAsync (PNG) │ ├─ postMessage ──▶ Uint8Array → Blob FormData.append() │ ├─ POST /api.php ──▶ encoder.py │ │ ◀── JSON response ────────┘ │ (base64 GIF + URL) ◀── postMessage ────┘ figma.notify("Done!") ``` --- ## Plugin Setup ### manifest.json ```json { "name": "GIF Encoder", "id": "your-plugin-id", "api": "1.0.0", "editorType": ["figma"], "main": "code.js", "ui": "ui.html", "documentAccess": "dynamic-page", "networkAccess": { "allowedDomains": [ "https://yourserver.com" ], "devAllowedDomains": [ "http://localhost:8888", "http://localhost:8000" ], "reasoning": "Uploads exported PNG frames to the GIF Encoder API." } } ``` Replace `yourserver.com` with your production server domain. `devAllowedDomains` allows localhost during development (MAMP on 8888, or PHP built-in server on 8000). --- ## Plugin Code ### code.js (Main Thread) Handles frame selection, PNG export, and communication with the UI. ```javascript figma.showUI(__html__, { visible: true, width: 420, height: 500 }); figma.ui.onmessage = async (msg) => { // ── Export selected frames as PNGs ── if (msg.type === 'EXPORT_FRAMES') { const selection = figma.currentPage.selection; if (selection.length === 0) { figma.notify('Select at least one frame.'); figma.ui.postMessage({ type: 'ERROR', error: 'No frames selected' }); return; } figma.notify(`Exporting ${selection.length} frames...`); const frames = []; for (const node of selection) { const bytes = await node.exportAsync({ format: 'PNG', constraint: { type: 'SCALE', value: msg.scale || 1 }, }); frames.push({ name: node.name.replace(/[^a-zA-Z0-9._-]/g, '_') + '.png', bytes: bytes, // Uint8Array — transfers via postMessage }); } figma.ui.postMessage({ type: 'FRAMES_READY', frames }); } // ── Handle results from the API ── if (msg.type === 'GENERATION_COMPLETE') { figma.notify(`Generated ${msg.count} GIF(s)`); } if (msg.type === 'GENERATION_ERROR') { figma.notify(`Error: ${msg.error}`, { error: true }); } if (msg.type === 'CLOSE') { figma.closePlugin(); } }; ``` ### ui.html (UI Iframe) Provides the settings UI and handles the HTTP upload to the GIF Encoder API. ```html

GIF ENCODER

``` --- ## How It Works 1. User selects frames in Figma and clicks **Export Selection & Generate GIF**. 2. `code.js` calls `exportAsync({ format: 'PNG' })` on each selected node. 3. The raw `Uint8Array` bytes are sent to the UI iframe via `postMessage`. 4. `ui.html` converts each `Uint8Array` to a `Blob`, wraps it in `FormData`, and POSTs to the API. 5. The API returns JSON with a base64-encoded GIF and a download URL. 6. The plugin shows a preview and download link inline. --- ## Batch Mode: Multiple GIFs To generate multiple GIFs from one export, organise frames by naming convention. For example, if your Figma frames are named: ``` banner_frame1 banner_frame2 banner_frame3 icon_frame1 icon_frame2 ``` You can group them in the config by prefix: ```javascript // In ui.html, after receiving frames: const groups = {}; msg.frames.forEach((frame) => { // Split on last underscore: "banner_frame1" → group "banner" const parts = frame.name.replace('.png', '').split('_'); parts.pop(); // remove frameN const groupName = parts.join('_') || 'default'; if (!groups[groupName]) groups[groupName] = []; groups[groupName].push(frame.name); }); // Build config with multiple groups const config = { groups: {}, quality: quality, }; Object.entries(groups).forEach(([name, files]) => { config.groups[name] = { files: files, delay: delay, loop: loop, }; }); ``` This generates one GIF per naming group in a single API call. --- ## Per-Frame Delays If different frames need different timings, encode it in the Figma frame name: ``` frame1_500ms frame2_200ms frame3_800ms ``` Then parse in `ui.html`: ```javascript const delays = []; const fileNames = []; msg.frames.forEach((frame) => { fileNames.push(frame.name); const match = frame.name.match(/_(\d+)ms/); delays.push(match ? parseInt(match[1]) : defaultDelay); }); config.groups[gifName] = { files: fileNames, delays: delays, // per-frame array loop: loop, }; ``` --- ## Troubleshooting ### CORS errors The GIF Encoder API sends `Access-Control-Allow-Origin: *` by default. If you still see CORS errors: - Check that the domain is in `allowedDomains` in `manifest.json`. - For localhost during development, use `devAllowedDomains`. - Make sure you're NOT manually setting `Content-Type` on the fetch — let the browser handle the multipart boundary. ### CSP blocked If requests silently fail, open the Figma developer console (Plugins → Development → Open Console) and look for CSP errors. Add the blocked domain to `allowedDomains`. ### "No frames selected" Make sure you've selected actual frame/group/component nodes in Figma before clicking generate. Text and vector nodes also work with `exportAsync`. ### Large exports timing out For many frames or high-resolution exports: - Lower the export scale (1x instead of 2x). - Export in smaller batches. - Increase PHP `max_execution_time` on the server. ### Uint8Array issues Only `Uint8Array` can cross the `postMessage` boundary in Figma plugins. If you see transfer errors, make sure you're not trying to send `ArrayBuffer`, `Blob`, or other typed arrays from the main thread. --- ## API Reference See the main [README.md](README.md) for full API documentation including: - Request/response schemas - curl examples for testing - Multipart vs JSON body modes - All available parameters (delays, quality, loop)