diff --git a/docs/technical-guide/developer/data-model/penpot-file-format.md b/docs/technical-guide/developer/data-model/penpot-file-format.md new file mode 100644 index 0000000000..431dbffaa2 --- /dev/null +++ b/docs/technical-guide/developer/data-model/penpot-file-format.md @@ -0,0 +1,628 @@ +--- +title: 3.02.01. Penpot file format (.penpot) +desc: Complete technical specification for the .penpot file format, including structure, schemas, and inspection methods. +--- + +# Penpot File Format (.penpot) + +The `.penpot` file format is Penpot's native export format for design files. It's a ZIP archive containing JSON metadata and binary assets, designed to be open, inspectable, and efficient. + +## Overview + +The `.penpot` format (version 3) uses a ZIP container with JSON files for metadata and binary files for media assets. This approach provides several advantages: + +- **Open and inspectable**: All metadata is human-readable JSON +- **Efficient**: ZIP compression reduces file size +- **Interoperable**: Standard formats enable third-party tooling +- **Versioned**: Clear versioning system for format and data evolution + +## Version History + +| Version | Description | Status | +|---------|-------------|--------| +| v1 | Custom binary format | Deprecated | +| v2 | SQLite-based format | Never released (internal only) | +| v3 | ZIP + JSON format | **Current** | + +## File Structure + +A `.penpot` file contains the following structure: + +``` +pencil.penpot (ZIP archive) +├── manifest.json # Root metadata +├── files/ +│ ├── {file-id}.json # File metadata +│ └── {file-id}/ +│ ├── pages/ +│ │ ├── {page-id}.json # Page metadata +│ │ └── {page-id}/ +│ │ └── {shape-id}.json # Individual shapes +│ ├── media/ +│ │ └── {media-id}.json # Media references +│ ├── colors/ +│ │ └── {color-id}.json # Library colors +│ ├── components/ +│ │ └── {component-id}.json # Library components +│ ├── typographies/ +│ │ └── {typography-id}.json # Library typographies +│ ├── tokens.json # Design tokens library +│ └── thumbnails/ +│ └── {tag}/{page-id}/{frame-id}.json # Thumbnail metadata +└── objects/ + ├── {uuid}.json # Storage object metadata + └── {uuid}.{ext} # Binary media files (png, jpg, etc.) +``` + +## Manifest Specification + +The `manifest.json` file is the root of the archive and contains metadata about the export. + +### Schema + +```json +{ + "version": 1, + "type": "penpot/export-files", + "generatedBy": "penpot/2.12.0", + "refer": "penpot", + "files": [ + { + "id": "uuid", + "name": "File Name", + "features": ["feature1", "feature2"] + } + ], + "relations": [] +} +``` + +### Fields + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `version` | integer | Yes | Format version (currently `1`) | +| `type` | string | Yes | Must be `"penpot/export-files"` | +| `generatedBy` | string | No | Penpot version that created the file | +| `refer` | string | No | Source system (typically `"penpot"`) | +| `files` | array | Yes | List of files in the archive | +| `files[].id` | UUID | Yes | File identifier | +| `files[].name` | string | Yes | File name | +| `files[].features` | array | Yes | Set of feature flags | +| `relations` | array | No | Library relationships `[file-id, library-id]` | + +### Example + +```json +{ + "type": "penpot/export-files", + "version": 1, + "generatedBy": "penpot/2.12.0-RC1-99-g40c27591f", + "refer": "penpot", + "files": [ + { + "id": "73b59a94-3ea3-8189-8007-3d36adc8c3e3", + "name": "Pencil | Penpot Design System", + "features": [ + "fdata/path-data", + "design-tokens/v1", + "variants/v1", + "layout/grid", + "components/v2", + "fdata/shape-data-type" + ] + } + ], + "relations": [] +} +``` + +## File Metadata + +Each file has a JSON file at `files/{file-id}.json` containing the file's metadata. + +### Schema + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | UUID | Yes | File identifier | +| `name` | string | Yes | File name | +| `revn` | integer | Yes | Revision number | +| `vern` | integer | No | Version number | +| `createdAt` | timestamp | Yes | Creation timestamp | +| `modifiedAt` | timestamp | Yes | Last modification timestamp | +| `deletedAt` | timestamp | No | Deletion timestamp (if soft-deleted) | +| `projectId` | UUID | No | Project identifier | +| `teamId` | UUID | No | Team identifier | +| `isShared` | boolean | No | Whether file is a shared library | +| `hasMediaTrimmed` | boolean | No | Whether media has been trimmed | +| `features` | array | Yes | Set of enabled feature flags | +| `migrations` | array | No | List of applied data migrations | +| `options` | object | No | File-level options | + +### Features + +The `features` field is a set of strings indicating which features are enabled in the file. Common features include: + +| Feature | Description | +|---------|-------------| +| `fdata/path-data` | Path data format | +| `fdata/shape-data-type` | Shape data type system | +| `design-tokens/v1` | Design tokens support | +| `variants/v1` | Component variants | +| `layout/grid` | Grid layout system | +| `components/v2` | Component system v2 | +| `plugins/runtime` | Plugin runtime support | + +### Migrations + +The `migrations` field lists all data migrations applied to the file. This ensures backward compatibility when the data model evolves. + +### Example + +```json +{ + "id": "73b59a94-3ea3-8189-8007-3d36adc8c3e3", + "name": "Pencil | Penpot Design System", + "revn": 28425, + "vern": 0, + "createdAt": "2025-12-10T10:24:18.686066Z", + "modifiedAt": "2025-12-10T12:13:49.799076Z", + "teamId": "b62e1aa4-d9a7-8147-8005-2813bed4056e", + "projectId": "f23add0e-6b77-8069-8005-41b48b93a5da", + "isShared": true, + "features": [ + "fdata/path-data", + "design-tokens/v1", + "variants/v1", + "layout/grid", + "components/v2", + "fdata/shape-data-type" + ], + "migrations": [ + "legacy-2", + "legacy-3", + "0001-remove-tokens-from-groups", + "0002-normalize-bool-content-v2" + ], + "options": { + "componentsV2": true + } +} +``` + +## Pages and Shapes + +### Page Structure + +Each page is stored at `files/{file-id}/pages/{page-id}.json`. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | UUID | Yes | Page identifier | +| `name` | string | Yes | Page name | +| `index` | integer | No | Page order in the file | +| `options` | object | No | Page options (guides, etc.) | +| `background` | string | No | Background color (hex) | +| `flows` | object | No | Prototype flows | +| `guides` | object | No | Ruler guides | + +### Shapes + +Individual shapes are stored at `files/{file-id}/pages/{page-id}/{shape-id}.json`. + +#### Shape Types + +Penpot supports 9 shape types: + +| Type | Description | +|------|-------------| +| `frame` | Container frame (artboard) | +| `group` | Group of shapes | +| `rect` | Rectangle | +| `circle` | Circle/Ellipse | +| `path` | Vector path | +| `text` | Text shape | +| `image` | Image | +| `bool` | Boolean operation | +| `svg-raw` | Raw SVG element | + +#### Base Shape Attributes + +All shapes share these base attributes: + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | UUID | Yes | Shape identifier | +| `name` | string | Yes | Shape name | +| `type` | string | Yes | Shape type (see above) | +| `selrect` | object | Yes | Selection rectangle `{x, y, width, height}` | +| `points` | array | Yes | Array of points `[{x, y}, ...]` | +| `transform` | array | Yes | 2D transformation matrix | +| `transformInverse` | array | Yes | Inverse transformation matrix | +| `parentId` | UUID | Yes | Parent shape identifier | +| `frameId` | UUID | Yes | Containing frame identifier | + +#### Geometry Attributes + +Shapes with geometry (frame, rect, circle, image, svg-raw, text): + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `x` | number | Yes | X position | +| `y` | number | Yes | Y position | +| `width` | number | Yes | Width | +| `height` | number | Yes | Height | + +#### Generic Attributes + +Optional attributes available on all shapes: + +| Field | Type | Description | +|-------|------|-------------| +| `fills` | array | Fill styles | +| `strokes` | array | Stroke styles | +| `opacity` | number | Opacity (0-1) | +| `blendMode` | string | Blend mode | +| `shadow` | array | Shadow effects | +| `blur` | object | Blur effect | +| `constraintsH` | string | Horizontal constraint | +| `constraintsV` | string | Vertical constraint | +| `r1`, `r2`, `r3`, `r4` | number | Border radius corners | +| `blocked` | boolean | Shape is locked | +| `hidden` | boolean | Shape is hidden | +| `collapsed` | boolean | Shape is collapsed | +| `componentId` | UUID | Component reference | +| `componentFile` | UUID | Component library file | +| `shapeRef` | UUID | Shape reference for components | +| `touched` | array | Modified component properties | +| `interactions` | array | Prototype interactions | +| `exports` | array | Export settings | +| `grids` | array | Grid configurations | +| `appliedTokens` | object | Applied design tokens | +| `pluginData` | object | Plugin-specific data | + +#### Type-Specific Attributes + +**Frame** +- `shapes`: array of child shape UUIDs +- `showContent`: boolean +- `hideInViewer`: boolean + +**Group** +- `shapes`: array of child shape UUIDs + +**Bool** +- `shapes`: array of child shape UUIDs +- `boolType`: string (`union`, `difference`, `exclude`, `intersection`) +- `content`: path data + +**Path** +- `content`: path data (SVG path commands) + +**Text** +- `content`: text content with formatting +- `positionData`: glyph position data + +**Image** +- `metadata`: object with `width`, `height`, `mtype`, `id` + +### Example Shape + +```json +{ + "id": "260aea33-4e55-808c-8007-3d4f2efe4230", + "name": "Rectangle", + "type": "rect", + "x": 100, + "y": 100, + "width": 200, + "height": 150, + "selrect": { + "x": 100, + "y": 100, + "width": 200, + "height": 150 + }, + "points": [ + {"x": 100, "y": 100}, + {"x": 300, "y": 100}, + {"x": 300, "y": 250}, + {"x": 100, "y": 250} + ], + "transform": [1, 0, 0, 1, 0, 0], + "transformInverse": [1, 0, 0, 1, 0, 0], + "parentId": "00000000-0000-0000-0000-000000000000", + "frameId": "00000000-0000-0000-0000-000000000001", + "fills": [ + { + "color": "#FF5733", + "opacity": 1 + } + ], + "r1": 8, + "r2": 8, + "r3": 8, + "r4": 8 +} +``` + +## Library Assets + +### Colors + +Stored at `files/{file-id}/colors/{color-id}.json`. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | UUID | Yes | Color identifier | +| `name` | string | Yes | Color name | +| `path` | string | No | Path in the library tree | +| `opacity` | number | No | Opacity (0-1) | +| `color` | string | Conditional | Hex color (for plain colors) | +| `gradient` | object | Conditional | Gradient definition | +| `image` | object | Conditional | Image fill definition | + +#### Plain Color Example + +```json +{ + "id": "abc123...", + "name": "Primary Blue", + "path": "Brand/Primary", + "color": "#0066CC", + "opacity": 1 +} +``` + +#### Gradient Color Example + +```json +{ + "id": "def456...", + "name": "Sunset Gradient", + "gradient": { + "type": "linear", + "startX": 0, + "startY": 0, + "endX": 1, + "endY": 1, + "stops": [ + {"color": "#FF6B6B", "offset": 0, "opacity": 1}, + {"color": "#4ECDC4", "offset": 1, "opacity": 1} + ] + } +} +``` + +### Components + +Stored at `files/{file-id}/components/{component-id}.json`. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | UUID | Yes | Component identifier | +| `name` | string | Yes | Component name | +| `path` | string | Yes | Path in the library tree | +| `mainInstanceId` | UUID | Yes | Root shape of main instance | +| `mainInstancePage` | UUID | Yes | Page containing main instance | +| `modifiedAt` | timestamp | No | Last modification | +| `objects` | object | No | Captured shapes (if deleted) | + +### Typographies + +Stored at `files/{file-id}/typographies/{typography-id}.json`. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | UUID | Yes | Typography identifier | +| `name` | string | Yes | Typography name | +| `fontId` | string | Yes | Font identifier | +| `fontFamily` | string | Yes | Font family name | +| `fontVariantId` | string | Yes | Font variant | +| `fontSize` | string | Yes | Font size | +| `fontWeight` | string | Yes | Font weight | +| `fontStyle` | string | Yes | Font style | +| `lineHeight` | string | Yes | Line height | +| `letterSpacing` | string | Yes | Letter spacing | +| `textTransform` | string | Yes | Text transform | + +### Design Tokens + +Stored at `files/{file-id}/tokens.json`. + +The tokens library contains: + +- **Sets**: Collections of tokens organized hierarchically +- **Themes**: Named combinations of token sets +- **Active Themes**: Currently applied themes + +#### Token Structure + +```json +{ + "sets": { + "core": { + "id": "uuid", + "name": "Core", + "tokens": { + "color": { + "primary": { + "id": "uuid", + "name": "primary", + "type": "color", + "value": "#0066CC" + } + } + } + } + }, + "themes": { + "light": { + "id": "uuid", + "name": "Light", + "sets": ["core"] + } + }, + "activeThemes": ["light"] +} +``` + +## Media and Storage Objects + +### Storage Objects + +Binary assets (images, fonts, etc.) are stored in the `objects/` directory. + +Each storage object has: +- `objects/{uuid}.json` - Metadata +- `objects/{uuid}.{ext}` - Binary content (png, jpg, svg, etc.) + +#### Metadata Schema + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | UUID | Yes | Storage object identifier | +| `size` | integer | Yes | File size in bytes | +| `contentType` | string | Yes | MIME type | +| `bucket` | string | Yes | Storage bucket | +| `hash` | string | No | Content hash (blake2b) | + +#### Example + +```json +{ + "id": "0039433d-adc8-430d-b2c3-d884dea6e050", + "size": 575, + "contentType": "image/png", + "bucket": "file-media-object", + "hash": "blake2b:77d447db38eb5daf31acb7344a504cacc6b79aa11855a00501d9475c595053d0" +} +``` + +### Media References + +File media references are stored at `files/{file-id}/media/{media-id}.json`. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | UUID | Yes | Media identifier | +| `name` | string | Yes | File name | +| `width` | integer | Yes | Image width | +| `height` | integer | Yes | Image height | +| `mtype` | string | Yes | MIME type | +| `mediaId` | UUID | Yes | Reference to storage object | +| `thumbnaillId` | UUID | No | Reference to thumbnail | +| `isLocal` | boolean | No | Whether media is local to file | + +### Thumbnails + +Page thumbnails are stored at `files/{file-id}/thumbnails/{tag}/{page-id}/{frame-id}.json`. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `fileId` | UUID | Yes | File identifier | +| `pageId` | UUID | Yes | Page identifier | +| `frameId` | UUID | Yes | Frame identifier | +| `tag` | string | Yes | Thumbnail tag | +| `mediaId` | UUID | Yes | Reference to storage object | + +## Plugin Data + +Plugins can store custom data on files, pages, shapes, components, colors, and typographies using the `pluginData` field. + +### Structure + +```json +{ + "pluginData": { + "plugin-id": { + "key1": "value1", + "key2": "value2" + } + } +} +``` + +The plugin ID is a keyword (e.g., `"my-plugin"`), and the values are string key-value pairs. + +## Inspecting a .penpot File + +### List Contents + +```bash +unzip -l design.penpot +``` + +### Extract and View + +```bash +# Extract to temporary directory +unzip design.penpot -d /tmp/penpot-inspect + +# View manifest +cat /tmp/penpot-inspect/manifest.json | jq . + +# List all files +find /tmp/penpot-inspect -name "*.json" | head -20 + +# View a specific shape +cat /tmp/penpot-inspect/files/*/pages/*/*.json | jq . +``` + +### Browser-Based Inspector + +For an interactive way to explore a `.penpot` file, try the [**Penpot file inspector**](/technical-guide/developer/data-model/penpot-file-inspector/). It runs entirely in your browser, displays a collapsible file tree, syntax-highlighted JSON, image previews, and clickable cross-references between shapes, components, colors, and media. + +### Quick Inspection Script + +```bash +#!/bin/bash +# Inspect .penpot file structure + +FILE=$1 +TMPDIR=$(mktemp -d) + +unzip -q "$FILE" -d "$TMPDIR" + +echo "=== Manifest ===" +cat "$TMPDIR/manifest.json" | jq '{type, version, files: [.files[] | {id, name}]}' + +echo -e "\n=== Files ===" +for f in "$TMPDIR"/files/*.json; do + echo "- $(jq -r '.name' "$f")" +done + +echo -e "\n=== Pages ===" +for f in "$TMPDIR"/files/*/pages/*.json; do + echo "- $(jq -r '.name' "$f")" +done + +echo -e "\n=== Storage Objects ===" +ls -lh "$TMPDIR"/objects/*.{png,jpg,svg} 2>/dev/null | wc -l +echo "media files" + +rm -rf "$TMPDIR" +``` + +## Cross-References + +- [Penpot file inspector](/technical-guide/developer/data-model/penpot-file-inspector/) - Browser-based interactive inspector +- [Data Model](/technical-guide/developer/data-model/) - Conceptual data model +- [Data Guide](/technical-guide/developer/data-guide/) - Working with data structures +- [Export/Import Files](/user-guide/export-import/export-import-files/) - User guide for exporting and importing + +## Source Code References + +The authoritative schema definitions are in the Penpot source code: + +- **Manifest**: `backend/src/app/binfile/v3.clj` (schema:manifest) +- **File**: `common/src/app/common/types/file.cljc` (schema:file) +- **Page**: `common/src/app/common/types/page.cljc` (schema:page) +- **Shape**: `common/src/app/common/types/shape.cljc` (schema:shape) +- **Component**: `common/src/app/common/types/component.cljc` (schema:component) +- **Color**: `common/src/app/common/types/color.cljc` (schema:library-color) +- **Typography**: `common/src/app/common/types/typography.cljc` (schema:typography) +- **Tokens**: `common/src/app/common/types/tokens_lib.cljc` (schema:tokens-lib) +- **Plugin Data**: `common/src/app/common/types/plugins.cljc` (schema:plugin-data) +- **Features**: `common/src/app/common/features.cljc` (schema:features) diff --git a/docs/technical-guide/developer/data-model/penpot-file-inspector.njk b/docs/technical-guide/developer/data-model/penpot-file-inspector.njk new file mode 100644 index 0000000000..5321f79fe0 --- /dev/null +++ b/docs/technical-guide/developer/data-model/penpot-file-inspector.njk @@ -0,0 +1,1401 @@ +--- +title: 3.02.02. Penpot file inspector +desc: Interactive browser-based inspector for .penpot (v3) files. Upload a file to explore its ZIP structure, browse JSON contents, preview images, and navigate cross-references. +--- + + + +
Upload a .penpot file to explore its contents. The inspector runs entirely in your browser — your file is never uploaded to a server.
This tool is a static page with no backend. When you upload a file, the browser:
+ +For full details on the file format, see the complete technical specification.
+ +Your file is processed entirely in your browser. Nothing is uploaded to any server. The only network request the page makes is fetching the JSZip library from cdn.jsdelivr.net on the first file upload.

Penpot export to a unique format that streamline the import and export of files and assets by being more efficient and interoperable.
+Penpot exports to a unique format that streamlines the import and export of files and assets by being more efficient and interoperable.
Unlike other design tools, Penpot's format is built on standard languages. The exported file is essentially a ZIP archive containing binary assets (such as bitmap and vector images) alongside a readable JSON structure. By avoiding proprietary formats, Penpot empowers users with autonomy from specific tools while enabling seamless third-party integrations.
+For a detailed look at what's inside a .penpot file, see The .penpot file format. Developers can also check the technical specification.
These formats can only be exported from version 2.3 or earlier versions, but can be imported to any Penpot version.
There are two different deprecated Penpot file formats in which you can import/export Penpot files. A standard one and a binary one. You always have the chance to use both for any file.
-The fast one. Binary Penpot specific.
The open one. A compressed file that includes SVG and JSON.
+The open one. A compressed file that includes SVG and JSON. This format was developed but never released to users.
How to export and import your Penpot files
-Understand what's inside a .penpot file and how to inspect it
+ +How to export elements from your design into different file formats
diff --git a/docs/user-guide/export-import/penpot-file-format.njk b/docs/user-guide/export-import/penpot-file-format.njk new file mode 100644 index 0000000000..6febaa47e5 --- /dev/null +++ b/docs/user-guide/export-import/penpot-file-format.njk @@ -0,0 +1,155 @@ +--- +title: The .penpot file format +order: 2 +desc: Understand the .penpot file format, what's inside, and how to inspect your design files. +--- + +Penpot's native file format is designed to be open, inspectable, and efficient. Unlike proprietary formats, you can always look inside a .penpot file to understand your design data.
+ +A .penpot file is a ZIP archive containing:
This means your design data is never locked in a proprietary format. You can always unzip a .penpot file and read the JSON to understand what's inside.
+ +When you unzip a .penpot file, you'll find this structure:
+ +your-design.penpot (ZIP archive)
+├── manifest.json ← Table of contents
+├── files/ ← Your design data
+│ ├── file-metadata.json
+│ └── file-data/
+│ ├── pages/ ← Each page of your design
+│ ├── colors/ ← Library colors
+│ ├── components/ ← Library components
+│ ├── typographies/ ← Library typographies
+│ ├── tokens.json ← Design tokens
+│ └── media/ ← Media references
+└── objects/ ← Images and binary assets
+The manifest is the table of contents. It tells you:
+The files/ directory contains all your design data organized by type:
The objects/ directory contains the actual binary files (PNG, JPG, SVG) referenced by your design. Each object has a JSON metadata file alongside the binary file.
Since .penpot files are just ZIP archives, you can inspect them with standard tools.
+ +1. List the contents without extracting:
+unzip -l your-design.penpot
+
+2. Extract to a temporary folder:
+unzip your-design.penpot -d /tmp/penpot-inspect
+
+3. View the manifest:
+cat /tmp/penpot-inspect/manifest.json | jq .
+
+4. Browse the structure:
+tree /tmp/penpot-inspect
+
+5. View a specific shape:
+cat /tmp/penpot-inspect/files/*/pages/*/*.json | jq .
+
+1. Right-click the .penpot file and select "Extract All..." or use 7-Zip.
+ +2. Browse the extracted folder to see the structure.
+ +3. Open any JSON file with a text editor or use a JSON viewer tool.
+ +You can use the Penpot MCP server to inspect .penpot files with AI assistance. The MCP server can read and analyze your design files, making it easy to understand complex designs or automate tasks.
+ +The .penpot format has evolved over time:
+ +| Version | +Description | +Status | +
|---|---|---|
| v3 | +ZIP + JSON format (current) | +✅ Current | +
| v1 | +Custom binary format | +⚠️ Deprecated | +
All current Penpot versions export in v3 format. Old v1 files can still be imported, but new exports will use v3.
+ +Inside a .penpot file, you'll see two different version numbers:
+ +manifest.json) — Tracks changes to the ZIP structure itself. Currently 1.Files also include a list of feature flags that indicate which capabilities are used. For example:
+ +design-tokens/v1 — Uses design tokenscomponents/v2 — Uses the v2 component systemvariants/v1 — Uses component variantslayout/grid — Uses grid layoutsThese flags help Penpot know what features need to be supported when importing the file.
+ +If you're building tools or integrations that work with .penpot files, the complete technical specification provides detailed schema definitions for every JSON file in the archive.
+ +Key resources:
+