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

18 KiB

title, desc
title desc
3.02.01. Penpot file format (.penpot) 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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "id": "abc123...",
  "name": "Primary Blue",
  "path": "Brand/Primary",
  "color": "#0066CC",
  "opacity": 1
}

Gradient Color Example

{
  "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

{
  "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

{
  "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

{
  "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

unzip -l design.penpot

Extract and View

# 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. 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

#!/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

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)