2026-05-22 12:00:44 +00:00
---
title: File Format
description: Technical reference for OpenPencil .fig and .pen document formats, Kiwi binary structure, import pipeline, and export behavior.
---
2026-02-28 08:38:27 +00:00
# File Format
## .fig File Structure
2026-03-08 10:53:33 +00:00
A `.fig` file is a ZIP archive containing a Kiwi-encoded binary message:
2026-03-08 11:33:56 +00:00
| Offset | Content |
|--------|---------|
| 0 | Magic header `fig-kiwi` (8 bytes) |
| 8 | Version (4 bytes, uint32 LE) |
| 12 | Schema length (4 bytes, uint32 LE) |
| 16 | Compressed Kiwi schema |
| … | Message length (4 bytes, uint32 LE) |
| … | Compressed Kiwi message — `NodeChange[]` (entire document) |
| … | Blob data — images, vector networks, fonts |
2026-02-28 08:38:27 +00:00
## Import Pipeline
2026-03-08 11:33:56 +00:00
```
.fig file → parse header → decompress Zstd → decode Kiwi schema
→ decode message → NodeChange[] → build SceneGraph
→ resolve blob refs → render on canvas
2026-02-28 08:38:27 +00:00
```
2026-02-28 14:54:56 +00:00
## Export Pipeline
2026-03-08 11:33:56 +00:00
```
SceneGraph → NodeChange[] → Kiwi encode → compress (Zstd/deflate)
→ build ZIP (header + schema + message + thumbnail.png)
→ write .fig file
2026-02-28 14:54:56 +00:00
```
2026-03-08 10:53:33 +00:00
Export uses < kbd > ⌘</ kbd >< kbd > S</ kbd > (Save) and < kbd > ⇧</ kbd >< kbd > ⌘</ kbd >< kbd > S</ kbd > (Save As) with native OS dialogs on the desktop app. The exported file includes a `thumbnail.png` required by Figma for file preview.
Compression uses Zstd via Tauri Rust command on desktop, with deflate fallback in the browser.
2026-02-28 14:54:56 +00:00
2026-02-28 08:38:27 +00:00
## Kiwi Binary Codec
2026-03-08 10:53:33 +00:00
The codec handles Figma's 194-definition Kiwi schema with `NodeChange` as the central type (~390 fields). Key components:
2026-02-28 08:38:27 +00:00
2026-03-08 10:53:33 +00:00
| Module | Purpose |
|--------|---------|
| `kiwi-schema` | Kiwi parser (from [evanw/kiwi ](https://github.com/nicolo-ribaudo/kiwi )), patched for ESM and sparse field IDs |
| `codec.ts` | Encode/decode messages using the Kiwi schema |
| `protocol.ts` | Wire format parsing and message type detection |
| `schema.ts` | 194 message/enum/struct definitions |
2026-02-28 08:38:27 +00:00
### Sparse Field IDs
2026-03-08 10:53:33 +00:00
Figma's schema uses non-contiguous field IDs (e.g. 1, 2, 5, 10 with gaps). The kiwi-schema parser handles this correctly.
2026-02-28 08:38:27 +00:00
### Compression
2026-03-08 10:53:33 +00:00
`.fig` files use Zstd compression for both the schema and message payloads. Decompression uses the `fzstd` library. For export, Zstd compression is offloaded to a Tauri Rust command on the desktop app (better performance, correct frame headers). In the browser, deflate via `fflate` is used as a fallback.
2026-02-28 08:38:27 +00:00
## Supported Formats
2026-03-28 12:42:21 +00:00
| Format | Open / Read | Save / Write | Export |
|--------|-------------|--------------|--------|
| `.fig` (Figma) | ✅ | ✅ | ✅ |
| `.pen` (Pencil) | ✅ | — | — |
| `.png` | — | — | ✅ |
| `.jpg` | — | — | ✅ |
| `.webp` | — | — | ✅ |
| `.svg` | — | — | ✅ |
| `.jsx` | — | — | ✅ |
2026-02-28 14:57:59 +00:00
2026-02-28 08:38:27 +00:00
## Clipboard Format
2026-03-02 12:31:25 +00:00
Copy/paste uses the same Kiwi binary encoding:
2026-02-28 08:38:27 +00:00
2026-03-08 11:33:56 +00:00
1. **Copy** — encode selected `NodeChange[]` to Kiwi binary, compress, write to clipboard as `application/x-figma-design` MIME type
2. **Paste** — read clipboard, decompress, decode Kiwi binary, create nodes in scene graph
2026-02-28 08:38:27 +00:00
2026-03-08 11:33:56 +00:00
Encoding happens synchronously in the copy event handler (not async Clipboard API) for browser compatibility. This enables bidirectional clipboard between OpenPencil and Figma.