The .mtcanvus file format
A .mtcanvus file is a complete copy of one canvas in a single file: the canvas description and every file the canvas uses. This page describes how the file is built, so that you can archive, inspect or index exports with your own tools. To export and import canvases in Canvus, see Snapshots and export.
A .mtcanvus file is not a zip archive. It is a MultiTaction container format: a short header, two compressed JSON blocks that describe the canvas, and the canvas's files stored one after another, unchanged.
Note
Canvus releases before 2.0 exported canvases as zip archives named *.mt-canvus-canvas.zip. Current Canvus versions cannot import these files: dropping one on a folder in the Canvas List fails with "Importing 1.x canvases is not supported".
Warning
A .mtcanvus file is not encrypted, even when the canvas has a password. Anyone who has the file can read its content with the information on this page. Store and transfer it as you would the original files.
Layout
The file is a sequence of blocks, in this order:
| Block | Content |
|---|---|
| Start key | The 10 bytes multisite followed by a zero byte |
| Format block | A UTF-8 JSON object followed by a zero byte |
| Canvas metadata | A 4-byte length, then the compressed metadata JSON |
| Canvas items | A 4-byte length, then the compressed item JSON |
| Assets | Zero or more asset records, one per file the canvas uses |
| End key | The 4 bytes end followed by a zero byte |
General rules:
- Numbers are unsigned integers in little-endian byte order.
- Text is UTF-8.
- A compressed block is in Qt's
qCompressformat: a 4-byte big-endian uncompressed length, followed by a zlib stream. Skip the first 4 bytes and any zlib library can decompress it.
Format block
The format block describes the container, not the canvas:
{
"created": "2026-06-19T13:44:56.848",
"version": "2"
}
version: the container version. Canvus 3.0 and later write version2. Version1was written by Canvus 2.x, and Canvus converts it on import.created: the local date and time the file was written.
Canvas metadata
The metadata block is a JSON object that describes the canvas as a whole. For example:
{
"id": "36b1ebf0-c49b-4f79-bf3e-aa9ad39739fc",
"name": "Test",
"type": "mt-canvus",
"version": "27",
"state": "active_autosave",
"preview": "26b417f84ef67060efe115a26b1ba86da7d888f61337c4ed694dded816e16096",
"size": "0"
}
id: the canvas ID.name: the canvas name. On import, Canvus can give the canvas a different name.type: alwaysmt-canvusfor a Canvus canvas.version: the version of the canvas data structure. This is not the same as the container version in the format block.preview: the hash of the asset that holds the canvas preview image (see Hashes).
Other fields can appear, for example when the canvas has a password. Ignore fields you do not recognize.
Canvas items
The item block is a JSON object with two fields:
rootNodeId: the ID of the canvas's root item.nodes: a flat list of every item on the canvas.
Each item has the same five fields:
{
"id": "3750d5fa-6577-4f6b-9bda-1307128d29c5",
"name": "3750d5fa-6577-4f6b-9bda-1307128d29c5",
"parentId": "55e81c3f-0ac0-47d3-804d-83ade0b71659",
"type": "canvus_note",
"attributes": [
{"name": "location", "value": "MakPRXYgV0U="},
{"name": "size", "value": "AACWQwAAlkM="}
]
}
type: the kind of item. Examples arecanvus_note,canvus_image_asset,canvus_pdf_asset,canvus_video_asset,canvus_annotation(ink),canvus_anchor,canvus_presentationandcanvus_canvas_background. The root item has the typecanvus_shared_canvas.parentId: the item this item belongs to. Items placed directly on the canvas have the root item as their parent; ink drawn on an image has the image as its parent.attributes: the item's properties.
An attribute holds a single value in value, or a list of values in values together with a count in size. Both are Base64-encoded binary data in little-endian byte order:
| Data type | Encoding | Example attributes |
|---|---|---|
| Number | 4-byte float | depth, scale, line-width |
| Point or size | Two 4-byte floats (x, y) in canvas pixels | location, size |
| Color | Four 4-byte floats (red, green, blue, alpha), from 0 to 1 | background-color, text-color, line-color |
| Text | A 4-byte length, the UTF-8 bytes, then zero bytes up to a multiple of 4 | title, original-filename, source |
For example, the location value MakPRXYgV0U= decodes to the two floats 2298.57 and 3442.03, the position of the note on the canvas.
Items that show a file (images, videos, PDFs, the canvas background) store the file's public hash as hexadecimal text in their source attribute (background-image for the canvas background). The hash links the item to its asset record.
Asset records
Each file the canvas uses is stored once, however many items show it. An asset record is:
| Field | Size | Content |
|---|---|---|
| Asset key | 4 bytes | ast followed by a zero byte |
| Hash length | 4 bytes | The length of the hash, 32 in version 2 |
| Hash | as given | The file's private hash (see Hashes) |
| File name length | 4 bytes | |
| File name | as given | The original file name, for example Workshop Title.png |
| File size | 8 bytes | The number of bytes that follow |
| File content | as given | The original file, byte for byte |
After the last asset record comes the end key. A reader that meets end instead of ast has reached the end of the file.
Hashes
Canvus identifies each file by two BLAKE3 hashes (version 2):
- The private hash is the BLAKE3 hash of the file content. Asset records store it, and the metadata
previewfield uses it. - The public hash is the BLAKE3 hash of the 32 bytes of the private hash. Items use it in
source.
To find the file behind an item, compute the public hash of each asset record's private hash and match it with the item's source. To check that an asset is intact, compute the BLAKE3 hash of its content and compare it with the private hash.
Read a .mtcanvus file
This Python 3 script lists the canvas and its files, and saves the files and the decompressed JSON to a folder. It uses only the standard library.
import json, struct, sys, zlib
from pathlib import Path
def read_zstring(f):
out = b""
while (c := f.read(1)) not in (b"\0", b""):
out += c
return out
def read_block(f):
(length,) = struct.unpack("<I", f.read(4))
return json.loads(zlib.decompress(f.read(length)[4:])) # skip the qCompress length
src, dest = Path(sys.argv[1]), Path(sys.argv[2])
dest.mkdir(parents=True, exist_ok=True)
with src.open("rb") as f:
if f.read(10) != b"multisite\0":
sys.exit("Not a .mtcanvus file")
print("Format:", json.loads(read_zstring(f)))
meta, items = read_block(f), read_block(f)
print("Canvas:", meta.get("name"), "-", len(items["nodes"]), "items")
(dest / "metadata.json").write_text(json.dumps(meta, indent=2))
(dest / "items.json").write_text(json.dumps(items, indent=2))
while (key := read_zstring(f)) == b"ast":
(n,) = struct.unpack("<I", f.read(4)); private_hash = f.read(n)
(n,) = struct.unpack("<I", f.read(4)); name = f.read(n).decode()
(size,) = struct.unpack("<Q", f.read(8))
out = dest / f"{private_hash.hex()[:12]}-{Path(name).name}"
out.write_bytes(f.read(size))
print(f" {name} ({size} bytes)")
if key != b"end":
sys.exit("Unexpected data before the end key")
Run it with the export file and an output folder:
python3 read_mtcanvus.py "Workshop.mtcanvus" workshop-files
The script reads each file into memory. For very large exports, copy the file content in chunks instead.
Compatibility
- Canvus reads every container version up to the one it writes. An older Canvus refuses a file written with a newer container version.
- New fields can be added to the format block, the metadata and the items. Readers should ignore what they do not recognize.
- The item types and attributes follow the canvas data structure, which changes between releases. The REST API reference describes the same items in a stable, documented form. Use the API when you need to create or change content, and treat
.mtcanvusfiles as read-only archives outside Canvus.