Skip to main content
Canonical format, version 1. Every example below is copied from the japanese-starter fixture, which is validated against the published JSON Schemas on every build.

The envelope

Every file in a course starts with the same two keys, not just the manifest:
They are omitted from the examples below for brevity. A reader should check them on each file it opens, because a file copied between courses carries its own version.

Versioning and migration

formatVersion is a monotonic integer, raised only for a breaking change. Studio upgrades in memory on read and only writes the upgraded shape when that file is next saved, so opening a course never rewrites it. These changes do not raise the version, and readers must tolerate them:
  • a new optional key on an existing file type;
  • a new exercise type, composition block type, or part content kind.

Unknown content

A reader that meets a content.kind, exercise type, or block type it does not know must keep the file verbatim rather than fail the course. Studio parses such a part as:
The lesson still opens, the part keeps its position in the order, the editor shows a non-editable placeholder, and the body file is left untouched on save. That is what lets an older build open a course authored by a newer one without destroying data. A known type carrying a malformed payload is still an error. Tolerance covers new vocabulary, not corruption.

Localized text

Any authored string may be a plain string in the course’s defaultLocale, or a map of locale tag to string:
This applies to the project title, subtitle, and description; unit titles; lesson, part, and part-content titles; exercise instructions; and the text of a literal text fragment. Taught content is not localized this way: it lives in records, where a language is a field and list items carry their own locale. Resolve with an exact tag match first, then the base language (pt-BR to pt), then any available entry. A writer that edits one locale must preserve the others.

Contributors

A contributor may hold several roles. roles is the list; role repeats the first entry so readers written against version 1 keep working.
The course’s own version and releasedOn (as YYYY-MM-DD) also live on project. Both are authored metadata and have nothing to do with formatVersion, which describes the file schema.

Collection

A collection defines fields and lists its record files (paths relative to the collection file).
See Content records for the field type reference.

Record

One record per file. A many field stores a list; each list item carries a stable id.

Asset

Each asset lives in its own directory with an asset.json and (when ready) the binary file.
A ready asset also records the digest of its stored file:
  • availability is ready or placeholder. A placeholder has file: null and names the file it expects in expectedFile.
  • sha256 and byteSize describe the file as stored in the project, not the file the author picked. Studio strips EXIF from images on import, which changes the bytes, so the digest is computed after the copy. Use it for integrity checks and as a cache key.
  • When ready, file names the binary in the same directory (for example original.svg). Resolve it relative to the asset’s folder.
  • Never embed binary data or absolute paths. Content references an asset by id only.
See Media for the type and supported formats.

Lesson and parts

A lesson is an ordered list of parts. Each part points at one file and declares its kind.

Composition

A rich-media composition is a list of blocks. Each block has a type, a binding, and presentation hints. Observed block types are content-card, media, and callout.

Exercise

The on-disk exercise JSON mirrors the TypeScript interfaces in Exercises: a common envelope (id, type, instruction, prompt, evaluation) plus a type-specific body. Two examples:
Correctness always refers to the stable IDs the exercise defines (option, token, pair, and blank IDs) or to accepted-value bindings, never to displayed text. See Resolving and rendering for how each evaluation kind is graded.