Figma Bridge
Figma no tiene una API pública de escritura. Esta habilidad utiliza un puente local: operaciones nombradas sobre HTTP, ejecutadas dentro de Figma por un plugin a través de la API de Plugin de Figma. Sin OAuth, sin tokens.
Repositorio: https://github.com/iggypcnfsky/figma-bridge — AGENTS.md, docs/ops-reference.md.
Precondiciones — comprobar antes de cualquier otra cosa
figma está en PATH, así que nunca busques el repositorio en el sistema de archivos:
figma doctor # repositorio, servidor, ventanas conectadas, img-forge + clave FAL en una sola ejecución
figma clients # id, archivo, página de cada ventana de Figma conectada
figma rules # restricciones del proyecto: tipo mínimo, cuadrícula, tamaño de frame, nomenclatura de versiones
Lee figma rules antes de diseñar. Contiene los límites no obvios — por ejemplo, "Poppins 20/25 es el mínimo" — que ninguna cantidad de lectura del lienzo te dirá. Trata "confirmed": false como una suposición que debes verificar con el usuario, y {} como "pregunta, no inventes".
Si falta el comando, usa node "$(figma where)/server.mjs" solo después de preguntar; no hagas find del repositorio ni adivines rutas.
pluginConnected: false→ detente. Pide al usuario que ejecutePlugins → Development → Figma Bridgeen la ventana del archivo que desea modificar. Reintentar no ayuda.503de/runsignifica lo mismo.504 PLUGIN_GONEsignifica que cerraron la ventana del plugin.409significa que varias ventanas de Figma están conectadas y no se ha elegido ninguna. Selecciona el archivo que nombró el usuario (--file <NOMBRE>) o un id de cliente (--client <ID>); nunca reintentes a ciegas, o podrías editar el archivo equivocado.
El bucle
- Orientarse.
./bin/figma status→ nombre del archivo, página actual, selección, todas las páginas. - Leer.
selection 2,node <id> 2,find {"name":"Header","allPages":true}. Nunca adivines unnodeId— los ids de Figma se ven como4022:2y no se pueden derivar. - Planificar. Decidir padre, tamaño, orden. Los nuevos nodos van a la página actual a menos que se especifique
parentId. - Escribir.
./bin/figma run '[{...},{...}]'. - Verificar.
./bin/figma export <id> out/check.png PNG 2y realmente observa la imagen.
Comandos
./bin/figma op '{"op":"status"}'
./bin/figma run '[{"op":"createFrame","name":"Card","width":320,"height":200,"fill":"#0B0B0F",
"props":{"cornerRadius":16,"layoutMode":"VERTICAL","paddingTop":24,"paddingLeft":24,"itemSpacing":12}}]'
./bin/figma run '[{"op":"createText","parentId":"12:34","characters":"Title","fontSize":24,"color":"#FFFFFF"}]'
./bin/figma op '{"op":"varFill","nodeId":"12:34","variableName":"color/bg/surface"}'
./bin/figma export 12:34 out/card.png PNG 2
./bin/figma image 12:34 out/cover.png FILL # archivo de imagen local → relleno (sin subida a la nube)
Operaciones: ping status pages variables styles fonts inventory · selection node find · createFrame createRect createEllipse createText createPage createInstance · update clone reparent remove goto group ungroup select · grids setGrids align distribute effects · applyStyle varFill · export imageFill · exec.
Parámetros completos: docs/ops-reference.md.
Atajos de trabajo de diseño que vale la pena conocer:
- grid significa CUADRÍCULA: cuando el usuario dice "poner una cuadrícula", quiere la cuadrícula de diseño cuadrada, no columnas:
{"op":"setGrids","nodeId":"…","grid":true}(celdas de 40 px, 40% por defecto) o"grid":40. Usacolumns/rowssolo cuando digan columnas, una cuadrícula de 12 columnas, o den valores de medianil/margen. Nunca simules una cuadrícula con rectángulos;applyStyleconkind:"grid"aplica un estilo de cuadrícula guardado. - filas de elementos: constrúyelos, luego
{"op":"align","nodeIds":[…],"mode":"TOP"}y{"op":"distribute","nodeIds":[…],"axis":"HORIZONTAL","gap":24}. - profundidad:
{"op":"effects","nodeId":"…","shadow":{"y":8,"blur":24,"color":"#00000033"}}o{"op":"effects","nodeId":"…","blur":{"type":"BACKGROUND","radius":20}}. - degradados:
{"op":"gradient","nodeId":"…","kind":"linear","angle":135,"stops":[[0,"#8B76FF"],[1,"#0E0E12"]]}— el ángulo 0 es izquierda → derecha, 90 es arriba → abajo. Nunca simules un degradado con rectángulos apilados. - sistema de diseño:
createInstancecon un id deCOMPONENT, luegoinstancePropspara variantes (lee los nombres primero) einstanceTextpara cambiar el texto sin desvincular;applyStylepara estilos. No redibujes lo que la biblioteca ya proporciona y no desvincules una instancia para "arreglar" su texto. - comprobaciones de espaciado:
{"op":"measure","a":"1:2","b":"1:3"}devuelve espacios por lado y distancia al centro; úsalo en lugar de exportar una captura de pantalla para juzgar la alineación. - estilos:
saveStylecrea o actualiza un estilotext/paint/effect/gridpor nombre, yapplyStylelo aplica. Crea el token una vez, aplícalo en todas partes. - responsivo:
sizingestableceHUG/FILL/FIXEDpor eje más min/max;constraintscubre hijos sin auto-layout (STRETCH/MIN/MAX/CENTER/SCALE). Lee ambos denodeantes de cambiarlos —sizingdevuelveskippedcon la razón de Figma cuando una propiedad no aplica.
Imágenes
¿Necesitas arte? Genéralo con el CLI hermano img-forge
(https://github.com/iggypcnfsky/img-forge), luego colócalo — la generación permanece fuera
de Figma para que la clave API nunca entre en el código del plugin y un render lento no pueda bloquear el lienzo:
cd ../img-forge
./img gen --prompt "abstract gradient cover, purple to black" --preset 16:9@2k --out out/cover.png
./img edit --prompt "make it night with neon accents" --ref out/cover.png # o editar un activo existente
cd ../figma-bridge
./bin/figma image 12:34 ../img-forge/out/cover.png FILL
FILL recorta al nodo, FIT encuadra con bandas, TILE repite, CROP respeta el recorte existente.
Para editar lo que el usuario seleccionó, expórtalo primero (./bin/figma export <nodeId> out/sel.png PNG 2) y
pasa ese archivo como --ref.
La ventana del plugin también tiene un panel Generate / Edit para humanos (modelo, preajustes de tamaño, costo, insertar).
No manejes el panel mediante programación — usa el CLI; expone más (máscara, variantes, ejecución en seco).
Los preajustes guardados en ese panel residen en figma-bridge/presets.json.
Construir sobre un borrador existente
Un borrador que "no está terminado" aún codifica decisiones: qué está al lado de qué, qué está agrupado, el orden de los elementos, el ritmo de espaciado. Léelo antes de diseñar, luego consérvalo.
{"op":"inventory"}— los frames, sus tamaños y estadísticas de tipografía.{"op":"structure","nodeId":"<frame>"}— hijos agrupados en filas con espacios y unreadingOrder; esa es la agrupación que el autor eligió.{"op":"node",…,"depth":2}da el mismo árbol con más profundidad.- Observa el patrón (un margen izquierdo que se repite, un título que siempre está sobre tres columnas) y consérvalo. Cambia lo que la solicitud necesita, deja el resto donde está.
- Informa las desviaciones que hayas hecho — "moví el pie de página 40 píxeles hacia arriba", no solo "mejoré el diseño".
Nunca normalices un borrador en silencio. Si la coherencia requiere mover cosas que el usuario colocó, dilo y pregunta; una diapositiva que se ve ordenada pero perdió sus agrupaciones es una regresión, no una mejora.
Reglas
- Leer antes de escribir. Mínimo dos llamadas: inspeccionar, luego mutar.
- Un lote = un pensamiento. Una operación no puede hacer referencia al resultado de una operación anterior, así que si necesitas
el
iddel nuevo nodo, divide en dos llamadas. - Respeta la disposición que heredas: el orden de los hermanos, la proximidad y la agrupación son intención. No reorganices un borrador para que se vea más ordenado a menos que te lo pidan; pregunta primero cuando la coherencia movería sus elementos.
- Nunca toques las capas existentes del usuario (
update,remove,reparent) sin instrucción explícita. Prefija tus propias creaciones:Bridge / …. - Una reforma va justo al lado del original, lo más cerca posible. Si te piden rehacer una
diapositiva/pantalla/sección: duplícala, llama
{"op":"placeNear","nodeId":"<nuevo id>","move":true,"gap":80,"name":"Bridge / v2 — <nombre>"}para que la copia aterrice en el primer lado libre en lugar de una coordenada adivinada, y deja el original intacto. Si la herramienta informafree: false, pregunta dónde debe ir. Reemplaza elementos en su lugar solo cuando el usuario lo pida explícitamente. gotocambia la vista del usuario — solo a petición.execes un último recurso. Funciona (el sandbox de Figma permitenew Function) y evita toda validación de parámetros. Devuelve solo valores serializables en JSON.- Limpia los artefactos de prueba, o pregunta si conservarlos.
Errores comunes
- Usa
localhost, nunca127.0.0.1: el manifiesto del plugin solo permitehttp://localhost:3847(devAllowedDomains); Figma rechaza las IP. El servidor escucha tanto en127.0.0.1como en::1. figma.currentPagees la página abierta en esa ventana de Figma — no un puntero guardado. Cambiar de pestaña cambia lo que ves en la siguiente llamada.- Las posiciones son relativas al padre, y omitir
parentIdsignifica la página actual. - Las preguntas de tipografía (qué fuentes, tamaños, recuentos) son una sola llamada:
{"op":"fonts"}; añade"allPages":truepara todo el archivo. No recorras los frames con tu propio script. - Una vista general de la presentación de diapositivas es también una sola llamada:
{"op":"inventory"}devuelve cada frame con geometría,minFontSize, fuentes, recuento de cuadrículas y rellenos — suficiente para elegir "frames 11–19" o encontrar texto por debajo de un tamaño mínimo. - Nunca indexes un campo que la referencia no liste.
summaryllevafont,fontSize,lineHeightyletterSpacingpara TEXT; todo lo demás proviene deexeccon el nombre real de la API. Los estilos mixtos son la cadena"MIXED", así que protégete con.get()y valores por defecto. - Un rectángulo no puede tener hijos; usa un frame cuando el texto o los iconos deban estar dentro de una forma.
- Sin variables locales →
variablesdevuelvecollections: []yvarFillfalla. Compruébalo primero. - El texto necesita una fuente:
createTextla carga medianteloadFontAsync; si la familia falta, usaInter. exporttiene un límite de 8 MB — bajascaleen lugar de reintentar.- Leer hijos de una página que no es la actual necesita
loadAllPagesAsync; el puente reintenta una vez automáticamente, pero prefierefindconallPages: true. - Después de editar los archivos del plugin (
code.js,ui.html) el usuario debe volver a ejecutar el plugin; los cambios enmanifest.jsonnecesitan una reimportación.