
Security News
PolinRider: North Korea-Linked Supply Chain Campaign Expands Across Open Source Ecosystems
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.
openfig-core
Advanced tools
Isomorphic .fig file parser — reads Figma binary format in Node.js and browsers
Isomorphic .fig file parser — reads Figma binary format in Node.js and browsers.
Parses .fig (Figma Design), .deck (Figma Slides), and .jam (FigJam) files into a traversable node tree. Zero Node.js dependencies — works in browsers via bundlers.
npm install openfig-core
import { parseFig, nodeId } from 'openfig-core';
import { readFileSync } from 'fs';
const data = new Uint8Array(readFileSync('design.fig'));
const doc = parseFig(data);
console.log(doc.header); // { prelude: 'fig-kiwi', version: 52 }
console.log(doc.nodes.length); // number of nodes in the file
// Traverse the node tree
for (const node of doc.nodes) {
const id = nodeId(node);
const children = doc.childrenMap.get(id) ?? [];
console.log(`${id} ${node.type} "${node.name}" (${children.length} children)`);
}
const resp = await fetch('/design.fig');
const data = new Uint8Array(await resp.arrayBuffer());
const doc = parseFig(data);
parseFig(data: Uint8Array): FigDocumentParse a complete .fig ZIP archive. Extracts canvas.fig, meta.json, thumbnail.png, and images/*.
parseFigBinary(data: Uint8Array): FigDocumentParse raw canvas.fig binary data (the blob inside the ZIP). Use this if you extract the ZIP yourself.
nodeId(node: FigNode): string | nullFormat a node's GUID as "sessionID:localID" (e.g. "1:127"). Returns null if the node has no GUID.
FigDocumentinterface FigDocument {
header: { prelude: string; version: number };
nodes: FigNode[]; // all nodes (flat array)
nodeMap: Map<string, FigNode>; // id → node
childrenMap: Map<string, FigNode[]>; // parent id → children
schema: any; // decoded kiwi binary schema
compiledSchema: any; // compiled schema (encodeMessage/decodeMessage)
rawChunks: Uint8Array[]; // raw length-prefixed binary chunks
message: any; // full decoded kiwi message
meta?: Record<string, any>; // meta.json contents
thumbnail?: Uint8Array; // thumbnail.png bytes
images: Map<string, Uint8Array>; // filename → image bytes
}
FigNodeinterface FigNode {
guid: FigGuid;
type: string; // FRAME, TEXT, ELLIPSE, SYMBOL, INSTANCE, ...
name: string;
phase?: string; // CREATED, REMOVED, etc.
parentIndex?: { guid: FigGuid; position: string };
size?: { x: number; y: number };
transform?: { m00, m01, m02, m10, m11, m12: number };
fillPaints?: FigPaint[];
textData?: { characters: string };
[key: string]: any; // open for all kiwi-decoded fields
}
import { parseFig, encodeFigParts, assembleCanvasFig, createFigZip } from 'openfig-core';
const doc = parseFig(data);
// Edit nodes...
doc.message.nodeChanges[2].name = "Renamed";
const parts = encodeFigParts(doc);
// Caller must zstd-compress the message (openfig-core stays isomorphic)
const messageCompressed = yourZstdCompress(parts.messageRaw, 3);
const canvasFig = assembleCanvasFig({
prelude: parts.prelude,
version: parts.version,
schemaCompressed: parts.schemaCompressed,
messageCompressed,
passThrough: parts.passThrough,
});
const figZip = createFigZip({
canvasFig,
meta: doc.meta,
thumbnail: doc.thumbnail,
images: doc.images,
});
// figZip is a Uint8Array — write to disk or trigger download
import { createEmptyFigDoc, encodeFigParts, assembleCanvasFig, createFigZip } from 'openfig-core';
const doc = createEmptyFigDoc();
// Add nodes to doc.message.nodeChanges...
const parts = encodeFigParts(doc);
const messageCompressed = yourZstdCompress(parts.messageRaw, 3);
const canvasFig = assembleCanvasFig({ ...parts, messageCompressed });
const figZip = createFigZip({ canvasFig, meta: doc.meta, thumbnail: doc.thumbnail });
encodeFigParts(doc: FigDocument): EncodedFigPartsEncodes a FigDocument into kiwi binary parts. Returns the raw message (caller must zstd-compress for chunk 1) and the deflate-compressed schema (chunk 0).
assembleCanvasFig(input: AssembleCanvasFigInput): Uint8ArrayAssembles the canvas.fig binary from pre-compressed chunks. Format: [prelude 8B][version uint32 LE][len+chunk0][len+chunk1][len+chunk2+]...
createFigZip(input: CreateFigZipInput): Uint8ArrayPackages canvas.fig + optional meta.json, thumbnail.png, and images/* into a .fig ZIP archive (store mode).
createEmptyFigDoc(): FigDocumentCreates an empty FigDocument with a bundled kiwi schema, a Document node, and a Page node. Ready for adding content and encoding.
Detailed documentation of the Figma binary format is in docs/:
| Doc | Covers |
|---|---|
| Archive structure | ZIP layout, canvas.fig binary, kiwi schema, encoding pipeline |
| Nodes | Node types, GUIDs, parentIndex, hierarchy |
| Shapes | ROUNDED_RECTANGLE, FRAME, transforms, geometry |
| Vector | VECTOR nodes, commandsBlob format, blob resolution, helper API |
| Text | TEXT nodes, styles, fonts |
| Images | Image storage, SHA-1 hashing, thumbnails |
| Colors | Color variables, palette |
| Overrides | Symbol overrides (text, image, nested) |
| Slides | Slide dimensions, cloning, ordering |
| Modes | Slides mode vs Design mode |
| Invariants | Hard rules and sentinel values |
| Research | Binary format analysis and references |
fflate — ZIP extraction + deflate decompressionkiwi-schema — Kiwi binary format decodingfzstd — Zstandard decompressionMIT
Figma is a trademark of Figma, Inc. This project is not affiliated with, endorsed by, or sponsored by Figma, Inc.
FAQs
Isomorphic .fig file parser — reads Figma binary format in Node.js and browsers
We found that openfig-core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.

Security News
Open source attacks are accelerating as AI coding agents pull in dependencies faster, with less human review.

Research
/Security News
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.