Skip to content
Maxtopia

The .maxtopia file format

Your data is portable, and the format is public. Here’s the full spec, with schemas and a reference implementation you can use to read or write the files yourself.

Maxtopia stores and shares data in a small, self-describing binary container wrapping gzip-compressed JSON. The format is versioned and backward compatible — files you create today will keep opening in future versions of the app.

The three file types

  • .maxtopia — a shareable workout or program (a template). This is what you send to a friend or client.
  • .maxtopiaresults — completed workout results, sent from a client back to a coach.
  • .maxtopiabackup — a full export of an app’s data: programs, history, custom exercises, and settings.

All three share the same container; only the fileType byte and the JSON payload schema differ.

Container structure

A fixed 12-byte header followed by the gzip-compressed JSON payload. All multi-byte integers are big-endian.

container layout
Offset  Size  Field           Notes
------  ----  --------------  -----------------------------------------
0       4     magic           ASCII "MXTP" (0x4D 0x58 0x54 0x50)
4       2     formatVersion   uint16, big-endian (currently 1)
6       1     fileType        uint8  (1 = program, 2 = results, 3 = backup)
7       1     flags           uint8  (reserved, must be 0)
8       4     payloadCRC32    uint32, big-endian, CRC32 of the payload bytes
12      ...   payload         gzip-compressed UTF-8 JSON

A reader must verify the magic bytes and the CRC32 of the payload before trusting a file. Unknown flags bits and unknown JSON fields must be ignored, never rejected — that’s what keeps the format forward compatible.

Payload schemas

Program (.maxtopia)

program.maxtopia (decompressed)
{
  "schemaVersion": 1,
  "id": "5E9C0B7A-1F2D-4C3E-9A10-7B2C4D6E8F00",
  "name": "Beginner Linear Progression",
  "author": "Maxtopia",
  "createdAt": "2026-05-21T10:00:00Z",
  "notes": "Three days a week. Add weight each session.",
  "weeks": [
    {
      "index": 1,
      "days": [
        {
          "name": "Day A",
          "exercises": [
            {
              "exerciseId": "barbell-back-squat",
              "name": "Back Squat",
              "sets": [
                { "reps": 5, "weight": 60, "unit": "kg", "rpe": 7 },
                { "reps": 5, "weight": 60, "unit": "kg", "rpe": 7 },
                { "reps": 5, "weight": 60, "unit": "kg", "rpe": 8 }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Results (.maxtopiaresults)

session.maxtopiaresults (decompressed)
{
  "schemaVersion": 1,
  "programId": "5E9C0B7A-1F2D-4C3E-9A10-7B2C4D6E8F00",
  "client": { "displayName": "Sam" },
  "sessions": [
    {
      "date": "2026-05-20T18:32:00Z",
      "dayName": "Day A",
      "entries": [
        {
          "exerciseId": "barbell-back-squat",
          "sets": [
            { "reps": 5, "weight": 62.5, "unit": "kg", "rpe": 8, "completed": true },
            { "reps": 5, "weight": 62.5, "unit": "kg", "rpe": 9, "completed": true }
          ]
        }
      ]
    }
  ]
}

Backup (.maxtopiabackup)

export.maxtopiabackup (decompressed)
{
  "schemaVersion": 1,
  "app": "Maxtopia",
  "appVersion": "1.0.0",
  "exportedAt": "2026-05-21T10:00:00Z",
  "settings": { "units": "kg" },
  "programs": [ /* WorkoutProgram[] — same shape as .maxtopia */ ],
  "history": [ /* CompletedSession[] — same shape as .maxtopiaresults */ ],
  "customExercises": [ /* Exercise[] */ ]
}

Versioning policy

  • The container formatVersion and each payload’s schemaVersion follow semver.
  • New optional fields are a minor bump. Readers ignore fields they don’t recognize.
  • We commit to backward compatibility forever: any file a released version of Maxtopia ever wrote will keep opening.
  • A breaking change (should it ever happen) is a major bump and a new file extension, so old files are never silently misread.

Reference implementation

A complete, dependency-free TypeScript module to read and write the container. Copy it into your own tooling — it’s public domain.

maxtopia.ts
// maxtopia.ts — read and write .maxtopia files. Zero dependencies.
// Works in modern browsers and Node 18+. Released under CC0 (public domain).
// Spec: https://maxtopia.app/file-format

const MAGIC = 0x4d585450; // "MXTP"
export const FORMAT_VERSION = 1;

export enum MaxtopiaFileType {
  Program = 1,
  Results = 2,
  Backup = 3,
}

export interface MaxtopiaContainer<T = unknown> {
  formatVersion: number;
  type: MaxtopiaFileType;
  payload: T;
}

function crc32(bytes: Uint8Array): number {
  let crc = 0xffffffff;
  for (let i = 0; i < bytes.length; i++) {
    crc ^= bytes[i];
    for (let j = 0; j < 8; j++) crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
  }
  return (crc ^ 0xffffffff) >>> 0;
}

async function gzip(data: Uint8Array): Promise<Uint8Array> {
  const cs = new CompressionStream('gzip');
  const out = new Response(new Blob([data]).stream().pipeThrough(cs));
  return new Uint8Array(await out.arrayBuffer());
}

async function gunzip(data: Uint8Array): Promise<Uint8Array> {
  const ds = new DecompressionStream('gzip');
  const out = new Response(new Blob([data]).stream().pipeThrough(ds));
  return new Uint8Array(await out.arrayBuffer());
}

export async function encodeMaxtopia<T>(
  type: MaxtopiaFileType,
  payload: T
): Promise<Uint8Array> {
  const json = new TextEncoder().encode(JSON.stringify(payload));
  const compressed = await gzip(json);

  const header = new DataView(new ArrayBuffer(12));
  header.setUint32(0, MAGIC, false);
  header.setUint16(4, FORMAT_VERSION, false);
  header.setUint8(6, type);
  header.setUint8(7, 0); // flags (reserved)
  header.setUint32(8, crc32(compressed), false);

  const out = new Uint8Array(12 + compressed.length);
  out.set(new Uint8Array(header.buffer), 0);
  out.set(compressed, 12);
  return out;
}

export async function decodeMaxtopia<T = unknown>(
  bytes: Uint8Array
): Promise<MaxtopiaContainer<T>> {
  if (bytes.length < 12) throw new Error('Not a .maxtopia file: too short');
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);

  if (view.getUint32(0, false) !== MAGIC)
    throw new Error('Not a .maxtopia file: bad magic bytes');

  const formatVersion = view.getUint16(4, false);
  const type = view.getUint8(6) as MaxtopiaFileType;
  const expectedCrc = view.getUint32(8, false);

  const compressed = bytes.subarray(12);
  if (crc32(compressed) !== expectedCrc)
    throw new Error('Checksum mismatch: the file is corrupt');

  const json = new TextDecoder().decode(await gunzip(compressed));
  return { formatVersion, type, payload: JSON.parse(json) as T };
}

License

This specification and the reference implementation above are released into the public domain under Creative Commons Zero (CC0 1.0). Build whatever you want on top of it — importers, exporters, analysis tools — no permission needed.