Each lesson lives in items/<lesson-id>.lesson.json.
{
"schema_version": 1,
"id": "what-is-git",
"type": "lesson",
"completion": { "mode": "manual" },
"duration_min": 8,
"objectives": ["o1"],
"blocks": [ ]
}
| Field | Required | Rules |
|---|---|---|
schema_version |
Yes | 1. |
id |
Yes | Matches the file name and the item ID in course.json. |
type |
Yes | "lesson". |
completion.mode |
No | manual (default), on_view, video (needs an upload or url video block), or steps (needs a Steps block). |
duration_min |
No | 0 to 10,000, or null. Added up into the course length. |
objectives |
No | Objective IDs, each defined in course.json. |
blocks |
Yes | An ordered list of up to 300 blocks. |
Each block is { "id": "<local id>", "type": "<type>", ... }, and block IDs are unique within the lesson. Fields not listed for a type are rejected. The lesson’s title comes from course.json and is shown as the page heading, so start Markdown headings at ##.
Block types
text
markdown is required.
{ "id": "intro", "type": "text", "markdown": "## Heading\n\nParagraph with **bold** and `code`." }
callout
kind (note, tip, important, warning, or caution) and markdown are required. title is optional, up to 80 characters.
{ "id": "tip1", "type": "callout", "kind": "tip", "title": "Think in snapshots", "markdown": "Short, one idea." }
code
code is required, up to 50,000 characters. Optional: language in lowercase (such as bash, python, js, php, json, sql, yaml, html, or css), filename (up to 120 characters), highlight_lines (line numbers starting at 1), and show_copy.
{ "id": "run", "type": "code", "language": "bash", "filename": "terminal", "code": "git init\ngit status", "highlight_lines": [1], "show_copy": true }
image
src (an assets/ path in the zip) and alt (1 to 300 characters describing what the image shows) are required. Optional: caption (up to 300 characters) and zoomable.
{ "id": "diagram", "type": "image", "src": "assets/branch-diagram.png", "alt": "Four commits on main with a feature branch after the second", "caption": "Branching." }
video
provider and src are required.
provider |
src |
|---|---|
youtube |
The video ID or a full YouTube link. |
vimeo |
The numeric ID or a Vimeo link. |
url |
A direct https:// link to an .mp4 or .webm file. |
upload |
An assets/ path to an .mp4 or .webm file in the zip. |
Optional: title (up to 200 characters), poster (an assets path), captions, transcript_md, and complete_at_percent (1 to 100, default 90: how much must actually be played to count as watched). captions is a list of { "lang": "en", "label": "English", "src": "assets/intro.en.vtt" }.
{ "id": "vid", "type": "video", "provider": "youtube", "src": "dQw4w9WgXcQ", "title": "Intro", "transcript_md": "Full transcript..." }
Only upload and url videos can be tracked. A lesson whose only video is on YouTube or Vimeo should use "completion": { "mode": "manual" }.
audio
provider (upload or url) and src are required. Uploads are .mp3, .m4a, .ogg, or .wav assets, and URLs are direct https:// links to one. Optional: title and transcript_md. Always include a transcript.
{ "id": "interview", "type": "audio", "provider": "upload", "src": "assets/interview.mp3", "title": "Interview with the maintainer", "transcript_md": "Full transcript..." }
download
files is required: 1 to 30 of { "src": "assets/...", "label": "...", "description": "..." }. label is 1 to 120 characters, and the optional description belongs to each file, up to 300 characters.
{ "id": "sheet", "type": "download", "files": [ { "src": "assets/cheat-sheet.pdf", "label": "Cheat sheet" } ] }
embed
url is required. title and caption are optional. YouTube and Vimeo links become players, and any other link becomes a link card.
{ "id": "more", "type": "embed", "url": "https://git-scm.com/book", "title": "Pro Git book", "caption": "Free online." }
steps
A hands-on lab checklist. steps is required: one or more of { "id", "text_md" }, each with an optional command (shown with a copy button) and output (the expected result, collapsed). title is optional.
{ "id": "lab", "type": "steps", "title": "Lab steps", "steps": [
{ "id": "pull", "text_md": "Download the image.", "command": "docker pull nginx:stable", "output": "Status: Downloaded newer image" },
{ "id": "check", "text_md": "Open <http://localhost:8080> and confirm the welcome page." } ] }
Learners’ ticks are saved per step ID, so keep step IDs stable. Renaming one clears that tick for everyone. Use "completion": { "mode": "steps" } to complete the lesson when every step is ticked.
Markdown
Text, callouts, transcripts, question stems, and explanations use CommonMark with GitHub extensions: tables, task lists, strikethrough, autolinks, and fenced code blocks.
- Link to your own files by asset path:
or[Download](assets/sheet.pdf). - Other links must use
http,https, ormailto. - Raw HTML is sanitized. Only harmless elements, such as
<details>,<summary>, and tables, survive. Scripts, iframes, inline styles, event handlers, andjavascript:links are removed.
Assets
Everything a lesson references must be in assets/, and must genuinely be the type its extension claims.
| Type | Extensions | Largest file |
|---|---|---|
| Images | png, jpg, jpeg, webp, gif (no SVG) |
5 MB |
| Video | mp4, webm |
500 MB |
| Audio | mp3, m4a, ogg, wav |
200 MB |
| Captions | vtt |
1 MB |
| Documents | pdf, zip |
50 MB |
| Text | txt, md, csv |
5 MB |
File names can use letters, digits, ., _, and -, up to 200 characters. Lowercase kebab-case is easiest. Unused assets are harmless, but a missing referenced asset fails publishing.