The .drawo container

Drawoble saves a drawing as a single .drawo file. This document describes the container it is wrapped in — the archive format, the entry names, the bytes an identification tool matches on, and where the version number lives. It does not describe the drawing itself.

Last updated: 20 August 2026

Short version. A .drawo is a ZIP archive whose first entry is an uncompressed file named mimetype containing the string application/vnd.drawoble.drawing+zip. That puts the media type at a fixed byte offset, which is what lets a tool tell a Drawoble drawing apart from any other archive.

1. Identity

Property Value
Media type application/vnd.drawoble.drawing+zip
File extension .drawo
Structured syntax suffix +zip, registered by RFC 6839
Parameters None, required or optional
Specification This document
Encoding Binary; text entries inside are UTF-8 without a byte order mark

The name sits in the vendor tree described by RFC 6838 section 3.2, in producer-then-product form, so a later Drawoble format registers as a readable sibling rather than beside a bare vendor name carrying no product segment. The +zip suffix is accurate rather than decorative: the container genuinely is a ZIP archive, and a tool that already knows how to look inside one loses nothing by treating a .drawo the same way.

2. Container layout

A .drawo is a ZIP archive with its central directory at the end of the file, as ZIP requires. Every entry except the first is DEFLATE-compressed. Entry names are the following, and no entry name is ever localised or renamed:

Entry Required Compression Contents
mimetype Yes, and first Stored The media type string, with no trailing newline. This entry carries no payload; it exists so the file can be identified.
manifest.json Yes Deflate Container metadata and the integrity anchor. See section 4.
drawing.json Yes Deflate The drawing itself. Its schema is out of scope for this document.
thumbnail.png No Deflate A preview image, at most 512 by 512 pixels. Regenerable and never authoritative — a reader that ignores it loses nothing.
assets/<id>.<ext> No Deflate Embedded binaries referenced by the drawing. PNG, JPEG and WebP take their own extension; every other media type is written .bin.

Entries are written in that order. Nothing outside the list above is written, and a reader is expected to ignore an entry it does not recognise rather than reject the file. Payloads are not compressed a second time inside the archive: the ZIP layer is the only compression, because doubling it costs processor time and saves nothing.

An asset's extension is the only record of its media type: neither the manifest nor the drawing carries one beside it. The mapping is deliberately small. image/png, image/jpeg and image/webp are written .png, .jpg and .webp; every other media type is written .bin. Reading inverts the same table, also accepting .jpeg as image/jpeg, and reports an extension it does not know as application/octet-stream. A media type outside those three therefore does not survive a round trip, which is worth knowing before embedding one.

3. Byte signature

A ZIP local file header is thirty bytes of fixed-width fields followed by the entry name and then the entry's data. Because the mimetype entry is written first, is stored rather than compressed, and carries no extra field, three positions near the start of every file Drawoble writes are constant:

Offset Length Bytes Meaning
0 4 50 4B 03 04 The ZIP local file header signature
30 8 mimetype The first entry's name, immediately after the fixed header
38 36 application/vnd.drawoble.drawing+zip That entry's content, stored uncompressed

The three positions must be matched together. The four bytes at offset 0 belong to every ZIP archive in the world, so a rule that matches them alone claims every archive on the machine. What identifies a Drawoble drawing is the media type at offset 38, and offsets 0 and 30 are what make that offset predictable.

Offset 38 holds only while the first entry carries no extra field, because a ZIP writer places extra-field bytes between the entry name and its content. Some writers add alignment or timestamp extra fields by default. Drawoble does not, so the three positions above describe every file Drawoble writes today, and they are what an identification rule should be cut from.

A file that does not match is not thereby something other than a .drawo. The mimetype entry was added after the format had already shipped, so drawings written before it carry no anchor at all, and their first entry is a DEFLATE-compressed one like any other. Drawoble opens those exactly as it always did, without a warning, and they gain the anchor the next time they are saved — there is no rewriting pass, so one can sit on disk indefinitely. To an identification tool such a file is an ordinary ZIP archive, which is the accepted price of not having had the anchor from the beginning. What a reader should refuse is the other case: an anchor that is present and names a different media type.

Here are the first eighty bytes of a real file. Everything shown is identical in every file Drawoble writes except the four bytes at offsets 10 to 13, which are the entry's modification timestamp in MS-DOS form. The checksum at offsets 14 to 17 looks variable but is not: it is computed over the media type string, which never changes.

00000000  50 4b 03 04 14 00 00 00  00 00 00 60 e7 5c 4c 05  |PK.........`.\L.|
00000010  0c 62 24 00 00 00 24 00  00 00 08 00 00 00 6d 69  |.b$...$.......mi|
00000020  6d 65 74 79 70 65 61 70  70 6c 69 63 61 74 69 6f  |metypeapplicatio|
00000030  6e 2f 76 6e 64 2e 64 72  61 77 6f 62 6c 65 2e 64  |n/vnd.drawoble.d|
00000040  72 61 77 69 6e 67 2b 7a  69 70 50 4b 03 04 14 00  |rawing+zipPK....|

Reading the fixed header across that dump: compression method zero at offset 8 is stored, the entry name length of eight at offset 26 is mimetype, and the extra field length of zero at offset 28 is what leaves the content at offset 38. The next local file header begins at offset 74, immediately after the media type.

4. The manifest

manifest.json is a JSON object with exactly the following fields, in snake case. Unknown fields are rejected rather than ignored — the manifest is the container's integrity anchor, and a reader that guessed at it would be guessing about whether the rest of the file is intact.

Field Type Meaning
version Integer, currently 1 The layout version of the container itself — the entry names and this manifest's own shape.
creator String Free text identifying the writer, for diagnostics. Never parsed.
schema_version Semantic version The version of the drawing payload. Independent of version, and the subject of section 5.
checksum sha256: and 64 lowercase hexadecimal digits SHA-256 over the exact uncompressed bytes of drawing.json.
created_at RFC 3339 timestamp When the file was written, in UTC.

The checksum covers drawing.json and nothing else — not the manifest, not the thumbnail, and deliberately not the mimetype entry. Widening it would redefine the field and invalidate every file written before the change, and the archive's own per-entry checksums already cover those bytes. The mimetype entry is an aid to identification, never a trust boundary: anyone able to edit it is equally able to recompute the checksum beside it.

5. Versioning

The media type declares no parameters, and it never will. There is no version parameter to read and none to write. A file written by a much later release of Drawoble is still application/vnd.drawoble.drawing+zip, and an identification rule built on the bytes in section 3 keeps matching it.

The version lives inside the file instead, as two independent numbers. version describes the container — the entry names above and the shape of the manifest — and changing it would be a change to this document. schema_version describes the drawing payload, moves with ordinary product releases, and is expected to. Reading one to learn about the other is a mistake.

The payload version is a semantic version, and Drawoble's own reading rules follow from it: a file whose major version matches the reader opens, with a warning if it was written by a newer minor release. A file from a different major version is not opened by guesswork. None of that is a constraint on the container, and a third-party tool is free to take a different view of a payload it understands.

Room for payload growth is one named region rather than a general tolerance, and it is the one part of the drawing this document has to name. The optional extensions object is that region: keys Drawoble does not understand are carried through a load and written back out unchanged, so what one release cannot interpret still survives a round trip through it. Everywhere else the payload is strict — a field the reader does not recognise is refused rather than ignored, and the file does not open at all. Anything a third-party writer wants kept, and cannot expect Drawoble to know about, therefore belongs under extensions rather than beside the fields it already knows.

6. What this document does not cover

The schema of drawing.json is not published. It is Drawoble's, it changes with the product, and nothing about identifying a file requires it. The same is true of the contents of embedded assets, which are ordinary images carrying their own formats. A tool that wants to know a file is a Drawoble drawing has everything it needs in sections 2 and 3; a tool that wants to read the drawing does not, and that is deliberate rather than an omission.

The container defines no encryption, no external references, no scripting and no macros. Everything a .drawo contains is data, and opening one executes nothing. A reader handling untrusted files should apply the usual archive precautions — bound the decompressed size, and refuse an entry name that escapes the directory it is extracted into.

Questions about this document, including anything a registry needs clarified, go to [email protected].