Skip to content

.mstack file format ​

The native, fully-editable project format. A .mstack file is a ZIP archive containing a JSON manifest and one PNG per layer buffer. You can open it in any other instance of this editor and pick up exactly where you left off.

Beta notice: the format is at schema v5 as of v0.15.0. While in beta, schema versions can break compatibility — once a .mstack file is on disk it's pinned to that version. Treat exports as work-in-progress.


Container ​

A standard ZIP file with the following layout:

project.mstack (ZIP)
├── manifest.json          # canonical project state (zod-validated)
├── thumbnail.png          # 256 px max preview for project listings
└── layers/
    ├── <bufferKey>.png    # one PNG per unique buffer key
    └── ...
  • MIME type: application/x-mstack
  • Extension: .mstack
  • ZIP library: fflate (compression level 6)

manifest.json ​

A JSON document validated by the zod schema. Top-level fields:

FieldTypeDescription
schemaVersionintCurrently 5. Refusal to load if not exactly this.
app{ name, version }Editor version that wrote the file
project{ id, name, canvasWidth, canvasHeight, createdAt, updatedAt }Project metadata
framesRecord<FrameId, Frame>All frames keyed by id
frameOrderFrameId[]Display order
activeFrameIdFrameIdLast-viewed frame
activeLayerIdLayerIdLast-active layer in that frame
spritesRecord<SpriteId, Sprite>Named subsets of frames
spriteOrderSpriteId[]Display order
activeSpriteIdSpriteId | nullFilter state
animationsRecord<AnimationId, Animation>Playback sequences
animationOrderAnimationId[]Display order
activeAnimationIdAnimationId | nullActive animation
primaryColorhex stringFG colour
secondaryColorhex stringBG colour
palettestring[]Hex palette entries
tilePreviewboolTile preview toggle
pixelPerfectboolPixel-perfect toggle
symmetryobjectSymmetry state
rampsarrayColor ramps
activeRampIdstring | nullActive ramp
colorMode'rgb' | 'indexed'Indexed mode
cyclesarrayColor cycles
customBrushesarrayCustom brushes (mask as base64)
frameTagsarrayFrame tags
onionTintBeforehex | nullOnion skin before tint
onionTintAfterhex | nullAfter tint

Frame shape ​

ts
{
  id: string,
  name: string,
  width: number,
  height: number,
  layers: Layer[],     // ordered bottom-up
  durationMs: number,  // default playback duration
  groups: LayerGroup[] // layer groups for this frame
}

Layer shape ​

ts
{
  id: string,
  name: string,
  visible: boolean,
  locked: boolean,
  opacity: number,        // 0-1
  blendMode: 'normal' | 'multiply' | 'screen',
  linkKey?: string,       // when set, this layer shares its buffer
  parentGroupId?: string  // when set, this layer is in a group
}

LayerGroup shape ​

ts
{
  id: string,
  name: string,
  visible: boolean,
  locked: boolean,
  collapsed: boolean,
  opacity: number,        // 0-1
  blendMode: 'normal' | 'multiply' | 'screen'
}

FrameTag shape ​

ts
{
  id: string,
  name: string,
  fromIndex: number,                                // 0-based, inclusive
  toIndex: number,                                  // 0-based, inclusive
  color: string,                                    // hex
  direction: 'forward' | 'reverse' | 'pingpong'
}

ColorCycle shape ​

ts
{
  id: string,
  name: string,
  slotIndices: number[],   // palette indices that rotate
  stepMs: number,
  enabled: boolean
}

CustomBrush shape ​

ts
{
  id: string,
  name: string,
  width: number,
  height: number,
  maskB64: string         // base64 of width*height bytes; 1 = paint, 0 = skip
}

Layer PNGs ​

The layers/ directory contains one PNG per unique buffer key:

  • For an unlinked layer, the buffer key equals the layer id.
  • For a linked layer, the buffer key is the layer's linkKey — a single PNG is shared by every layer with that link key.

Each PNG is the same dimensions as the frame's width × height and contains RGBA pixels.

This dedup keeps file sizes reasonable for projects with linked backgrounds across many frames.


thumbnail.png ​

A preview rendered at the moment of save, capped at 256 px on the longest dimension and using nearest-neighbour scaling. Used in project listings (e.g. the File → Open project dialog) so you can identify projects without opening them.

The thumbnail is generated from the active frame's composed layers at save time.


Versioning policy ​

While the editor is in beta:

  • Each schema bump (v5 → v6 → …) is potentially breaking.
  • The reader rejects any file with schemaVersion ≠ the editor's current value. There's intentionally no migration ladder.
  • After 1.0, the policy will switch to: forward-compatible reads (always work) + explicit migrations on bumps.

For now, treat your .mstack files as transient. If you need long-term archival, also export to PNG / GIF / sprite sheet so the rendered output isn't lost when schemas change.


Motestack is a personal hobby project. The editor and these docs ship under no warranty — back up your `.mstack` files.