Andrey Antukh 4c7863d6c8
📚 Add .penpot format docs and inspector tool (#10674)
* 📚 Add comprehensive documentation for .penpot file format (v3)

Create user-facing documentation for the .penpot binfile format to help
developers and power users understand and inspect the ZIP+JSON structure.

- Add technical specification with complete schema reference for all JSON
  files (manifest, file metadata, pages, shapes, library assets, storage
  objects, plugin data)
- Add user-friendly overview explaining the format structure, inspection
  methods, and version history
- Update export-import-files.njk to clarify current format is not deprecated
  and note that v2 was never released
- Add cross-links between documentation pages
- Include source code references to authoritative malli schemas

AI-assisted-by: qwen3.7-plus

* 📎 Add playwright dependency to the root package.json

* 📚 Add browser based .penpot file inspector to docs

Add a client-side, no-build inspector for .penpot (v3) files. The tool
runs entirely in the browser: JSZip is loaded lazily from a CDN, the
file is never uploaded. It provides a collapsible file tree, syntax-
highlighted JSON viewer with search, image previews, a summary panel
with file/shape/storage counts, shape summary cards with color swatches,
and clickable UUID cross-references with backlinks.

The inspector is added at
docs/technical-guide/developer/data-model/penpot-file-inspector.njk
and linked from the format spec and the user-facing format page.

AI-assisted-by: minimax-m3

* 📚 Use full page width and 2-column layout for inspector

The inspector previously sat inside the docs site's 42rem content
column, which forced a stacked layout and wasted the wide viewport.

- Hide the docs page-navigation sidebar on this page via :has()
- Use a 2-column CSS grid: file tree (280px, sticky) on the left,
  content (flex: 1) on the right
- Restore the directory tree as a permanent left-rail navigation
  (it was removed when the picker was first introduced, then added
  back as a collapsible panel; a sticky rail is a better fit now
  that horizontal space is plentiful)
- Remove the tree toggle button — the tree is always visible
- Collapses to 1 column on viewports below 900px

AI-assisted-by: minimax-m3

* 💄 Indent nested JSON values in inspector

The JSON tree view in the inspector was rendering all properties at
the same indent level, making it hard to see which values were
nested inside objects or arrays. Root-level properties and deeply
nested ones looked identical.

Add padding-left and a subtle vertical guide line to .jchildren so
each nesting level is visually distinct. Bump the toggle column to
1.2em and add a touch of vertical padding to .jrow for breathing
room.

AI-assisted-by: minimax-m3
2026-07-20 14:45:59 +02:00

629 lines
18 KiB
Markdown

---
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)