Skip to content

Project & File Formats

Surface Labs reads and writes four file formats of its own:

ExtensionWhat it is
.surfacelabsThe project archive: all materials, subgraphs, embedded images, fonts, the preview model and export settings in one portable file
.surfacenodeA single node or subgraph packaged for sharing, with a preview thumbnail
.surfacethemeAn exported color theme
.mcpbA one-click Claude Desktop extension bundle exported by the MCP server settings page

A project is a single ZIP archive. Its internal structure:

EntryContents
manifest.xmlFormat version (currently 1.0), app version stamp, project name, created and last-modified dates, and the project’s tags written in full (id, label, color) so tags survive moving between users
graph/material.xmlThe active material: every node with parameters, seed, resolution mode and override, bit-depth mode and override, ports and port defaults, plus all connections
graph/m<n>.xmlOne file per inactive material in the project
materials.xmlThe material list: each material’s name, and which index is active
export_presets.xmlThe project’s saved export settings (format, prefix, engine, material type, packed/individual toggles, normal override, disabled channels)
exposed_params.xmlThe project’s exposed-parameter list
view_state.jsonCanvas pan/zoom plus the 3D preview’s camera and render settings
subgraphs/<id>.xmlOne file per subgraph definition
thumbnails/__project__.pngThe rendered material image used as the project’s Hub card, written when the graph has a PBR Output
media/<name>Imported bitmaps used by Image nodes, referenced as media:<name>, plus the model imported into the 3D preview
fonts/<name>Imported fonts used by Text nodes, referenced as font:<name>

Per-node preview images are not written. Encoding one PNG per node meant a GPU-to-CPU readback, a resize and an encode for every node in the graph on every save, which stalled large projects; nothing read them back, because canvas previews re-render when a project opens. The only image in the archive is the project’s Hub card.

The graph XML also carries canvas furniture: Jump Nodes, comment boxes (position, size, background color and opacity, text) and freehand annotation strokes.

The archive is zipped and written on a background isolate, so autosaving a project with tens of megabytes of embedded media does not freeze the editor.

Everything a project depends on travels inside it, so a .surfacelabs you hand to someone else opens the same way it does for you:

  • Imported bitmaps used by Image nodes.
  • Fonts used by Text nodes, so a Text node typesets identically on a machine that does not have the font installed.
  • The 3D preview model, when you import your own .obj or .glb.
  • Tags, written into manifest.xml in full (id, label, color) rather than as bare ids, so they arrive with the labels their author chose.

Embedded assets are pruned on save: one no longer referenced by any material or subgraph is dropped rather than carried forever, so deleting or repointing an Image or Text node stops inflating every later save. The prune reads every material and subgraph, not just the active one, so a bitmap used only by a material you have not switched to is never removed, and it only drops an asset once a saved copy exists to fall back on. The preview model is named by view_state.json rather than by a node parameter, and is kept on that basis.

A missing manifest.xml or graph/material.xml is a hard error. Unknown node types are skipped with a warning rather than aborting the load. Missing optional entries degrade gracefully: no materials.xml resets to a single material, no view_state.json clears the pending view state, no export_presets.xml restores export defaults. A corrupt view_state.json is ignored rather than failing the load. A project that fails to open is copied into the crash directory automatically so it can be attached to a bug report.

A project last saved when JPEG was still an export format loads with its format reset to PNG: an unrecognized format name falls back rather than failing, and PNG is the closest lossless equivalent. See Exporting.

New projects from the Project Hub go into a Surface Labs folder inside your Documents folder, created on first use. (On systems where a Documents folder cannot be resolved, the app falls back to $HOME/Documents, then the app-support directory, then the home directory.) Projects can be moved and opened from anywhere afterwards.

A project can hold several materials. The Material Library panel lists them: tap one to switch (the live graph is snapshotted into the outgoing entry first), Add Material creates an empty one, and deletes an inactive one. The active material and the last remaining material cannot be deleted.

Only the active material is live in the editor, and the in-app export dialog only ever exports a node from the active material. Exporting every material in one pass, with each non-active material’s files suffixed by its name, exists only through the MCP and command-line surface, not in the app UI. See Multi-Material Projects for working with materials in the editor.

A small ZIP holding one node:

EntryContents
node.xmlThe node’s type, label, all parameter values, seed, resolution mode and override, bit-depth mode and override, and its ports including values set directly on unconnected inputs
thumbnail.pngThe node’s last rendered output, written only when the node has already been evaluated

Connections are not part of the package: it describes one node in isolation. A missing thumbnail is not an error; previews regenerate. Unlike a .surfacelabs, a .surfacenode carries no embedded assets and no tags - so a node that depends on an imported bitmap does not bring the bitmap with it, and any tags you applied to it stay on your machine. See Sharing Nodes for how to export and import these packages.

A plain, indented JSON file (not a ZIP) holding one interface theme: its id, name, light/dark brightness, and one hex color per interface token, covering surfaces, text, borders, accent, status colors, overlay scrim, and the per-type node-connection colors. Exported and imported from Settings → Appearance. Themes exported by older builds are still readable: tokens added later fall back to their defaults instead of failing the import. A file that cannot be read or does not match the expected shape fails the import rather than partially applying.