MastriaDocs

Courses & content authoring

Courses on Mastria are markdown-first. Your content is plain text you can version, review, and take with you — never locked in a proprietary editor. This page covers the writing workflow: structure, the editor, publishing, the Courses page, the lesson library, and templates.

Structure

An academy contains courses; a course contains sections; a section contains lessons. Lessons are the unit learners read and complete.

SQL Fundamentals            ← course
├── Getting started         ← section
│   ├── Welcome             ← lesson
│   └── Why DuckDB
└── Your first queries
    ├── Selecting data
    ├── Aggregations
    └── Joins

Courses and lessons have stable, readable URLs (/courses/sql-fundamentals/lessons/joins). Lesson URLs survive moving a lesson between sections, so links you share stay valid as a course evolves.

Writing lessons

Lessons are GitHub-flavored markdown:

  • Headings, lists, bold, italic, links, images
  • Fenced code blocks with language tags (sql, python, …)
  • Tables and blockquotes
  • Callouts, in GitHub's alert syntax: > [!NOTE] alone on the first line of a blockquote (also TIP, IMPORTANT, WARNING, CAUTION). They render here the way github.com previews them, so repo-synced content looks the same in both places.
  • Hands-on labs and quizzes, as fenced ```lab and ```quiz blocks of YAML — see Lab authoring and Quizzes.

If your team can write a README, they can write a course.

Videos

Videos come from where they already live. Paste a YouTube, Vimeo, Loom, Wistia, or Mux link (player.mux.com/<playback-id> — your Mux Data analytics keep working), or a direct .mp4/.webm file or .m3u8 stream URL (Cloudflare Stream, your own CDN), on its own line, and learners get a player. Nothing loads from the provider until they press play, so lesson pages stay fast and cookie-quiet. The same URL inside a sentence stays an ordinary link, and so does anything that can't embed — the editor tells you why while you type.

There's no video upload, on purpose: your channel keeps its views and analytics, and your academy never meters streaming. Each video can carry a transcript so your docs reviews can check what it says; you fetch or paste it once under the editor, and nothing is pulled automatically. See Content freshness.

Draft vs. published

Work in progress is never visible to learners:

  • Every lesson has a working copy and a published copy — the text and the title. Learners only ever see the published copy; you can edit and rename freely behind the scenes, and a rename alone marks the lesson Changed like any edit.
  • Every lesson wears one of three states, everywhere in the admin: Draft (never published), Published, or Changed (published, with newer edits waiting).
  • Courses are also draft or published as a whole. A draft course is invisible even if some lessons are published.
  • Publishing is an explicit act, never a side effect of saving.

Important

Changed is the state to watch: it means learners are seeing an older version than the one you're editing. The boundary is stated where you work — lesson text and titles wait for publish, while the order of lessons, section names, and course settings apply to the published course as you change them (the outline says so on a published course, and so does Review & publish).

Deleting is safe, for lessons and courses alike. Brand-new drafts disappear for real; anything that has ever been published moves to a Deleted drawer instead — learners lose access immediately, but history and learner records stay intact, and you can restore it at any time, exactly as it was.

Duplicate anything

A lesson (the copy opens instantly, as a draft with fresh history), a section with its lessons, or a whole course — a private course's copy stays private. Accidental copy? Fresh drafts can be deleted for real with an inline confirm; anything with publish history moves to the Deleted drawer instead.

Version history

History is automatic: every publish snapshots the lesson. A tool release broke your course? Open the history drawer, restore any earlier version to your draft, review, and republish. The bad version stays in history; nothing is ever rewritten.

The course workspace

Staff members get an Admin area on your academy domain (/admin). Opening a course lands you in a single workspace: the course outline stays pinned on the left — a changed lesson wears a pencil after its title, a draft reads dimmed, lint errors a red triangle — with a health strip ("7 published", "2 changed", "1 error" — each count a click-to-filter; "empty" and "unfinished" lessons are counted apart, as Review & publish groups them), and the editor fills the rest. Switching lessons is one click. Your unsaved keystrokes are saved automatically before every switch, and the switch waits for that save — if it can't complete, you stay on the lesson with the reason shown in the outline — so nothing is ever lost to navigation.

Editing is markdown with power tools, not a rich-text editor:

  • A formatting toolbar (bold, headings, lists, quotes, links, inline code — plus ⌘B and ⌘I) that writes markdown into your document. There are deliberately no font pickers, sizes, or colors: your academy's design renders every lesson on-brand, in light and dark mode, and your content stays exportable plain text forever.
  • An Insert menu for the rarer blocks — code, tables, images, videos, callouts — each stamped in as valid markdown.
  • An Add lab menu: pick what learners will write — SQL (DuckDB), Python (Pyodide), Code-only (any language) — or Embedded lab (your own page), and a complete, valid lab skeleton lands with an id that's unique in the lesson. Once a lesson has a lab, the Exercise, Hint and Check buttons grow it at your cursor without touching anything else you wrote.
  • A Quiz menu that creates quizzes, adds questions (⌘⇧Enter inside a quiz adds the next one; Tab walks the fields), and opens question management.
  • Sections you can shape: click a section name in the outline to rename it, collapse sections you're not working in (remembered per course), and move a lesson to another section from the Section picker beside its title. The outline keeps Review & publish, View published course, Course settings, and Course history pinned at its foot however long the course grows, and folds behind an Outline bar on small screens.
  • ⌘K jumps to any lesson by fuzzy title — or creates one from whatever you typed. New lesson in the outline is one click to a blinking cursor.
  • Live validation as you type: errors (like a broken lab block) mark the exact line and block publishing; quality nudges (a lab missing its solution, a stray # heading competing with the lesson title) warn without blocking.
  • Split preview shows the rendered draft beside the editor — the real learner pipeline, refreshed each time autosave settles (pause it while you run a lab there). Preview draft as learner opens the full page, and View published course opens what learners see right now, without your unpublished changes.
  • Repo-synced lessons open read-only, with an Open source in GitHub button: the repo is the source of truth, and merging is how they change. Experiment locally lets you try edits as a scratch draft — previewable, never published, replaced by the next sync — with copy and download buttons to carry the result to the repo.

Note

In a preview, labs run but Check my work belongs to learners on the published lesson. Test lab in the editor is what verifies your checks — see Lab authoring.

Tip

Nothing is lost to a replacement. If the draft changed in another tab, the editor pauses and lets you copy your text, download it, compare it with the latest, and then load the latest; your version is kept in the browser and offered back under the editor. Restoring a historical version from History keeps your current draft the same way.

Review & publish

Publishing scales from one lesson to one deliberate release. Review & publish (the first row at the foot of the outline) is the launch path:

  • In the editor, Publish ships the lesson you're on — no ceremony — and tells you whether learners can see it yet (a lesson published inside a draft course waits for the course to open).
  • Review & publish shows three things before you act. Availability: whether the course is open to learners, who can open it (listed, unlisted, or hidden; everyone or selected accounts), and how many lessons are published — with Unpublish course here, naming what learners lose. What will publish: every draft or changed lesson as a row with its change size (+12 −3 lines, "new lesson", or "title only"), a link to the full diff, and a checkbox. What's unfinished: rows grouped ready / unfinished / empty / blocked.
  • A draft course opens in the same act: tick Also open the course to learners in this release and the button reads "Publish N lessons and open the course". A course can't be opened with nothing published in it.
  • Add an optional publish note ("Updated for product v2.1"), press publish, and the outcome stays on the screen: which lessons went live, which were refused (and why), and whether learners can see them now — with links to the published course and to history.
  • Course history shows every publish grouped by release — when, by whom, with what note, and which lessons — so "what changed in this course last month" is one page, with a diff per lesson.

Important

Only ready lessons start selected, so an empty lesson from a pasted outline or one still carrying a TODO never ships by accident (you can still tick it deliberately). Lessons that fail validation can't be selected at all, with the reasons on the row. Labs read Test failed (the solution doesn't pass its own checks) apart from not tested since the last edit — a reminder to press Test lab, never a blocker.

For repo-synced courses the same screen is a sync report: merging to your branch is the release act, and the commit message becomes the release note in course history. See Courses as code.

The Courses page

Admin → Courses is a table built for an academy of a hundred courses, not ten: 25 per page, sortable by title, created, or last updated (the newest of the course itself and any of its lessons, shown as "3 h ago" with the exact time on hover), and a search box that matches titles, URLs, and tags — partial, case-insensitive; the row shows its URL and tags, so a hit is never a mystery.

Filter tabs answer the management questions:

  • Status — published or draft.
  • Needs publishing — courses with draft lessons or unpublished edits.
  • Audience — everyone, restricted, unlisted, hidden, or closed to new learners.
  • Source — admin or repo, shown once a synced course exists.

Each row shows:

  • Course — the title with its attributes (Featured, Coming soon, Synced from repo), the course URL with a copy button, its tags, and a View link only when a public page exists (a live course or a coming-soon teaser — drafts have no public page yet).
  • Status — Published or Draft, as quiet grey text, and an amber "N to publish" badge when lessons are waiting (it opens Review & publish). A blank draft isn't counted (there's nothing in it to release), but a published lesson whose text you cleared is — publishing it would blank the lesson for learners. The rail's Review & publish button and the release screen's headline count the same lessons. Amber is kept for what needs you, so the courses waiting on a release stand out down the column.
  • Lessons — how many are published, then what's pending (drafts, changed) and what's empty.
  • Access — what learners can actually reach: everyone, N accounts, unlisted, hidden, existing learners only, or not open.

Note

"Published" alone never means learners can open a course. The Access column is what they can reach; the Status column is what you've released.

The Actions menu on a row leads where each entry says: the editor, a preview of the first lesson, the live page (when there is one), Review & publish (with the pending count — the way to release changes), the course and access settings, duplicate (copies get distinct names: "(copy)", "(copy 2)", …), and delete. A draft course with published lessons also offers Open to learners: that opens the course now with what's already published and leaves pending edits unpublished — it is not a way to release them.

Unpublish and delete confirm first and name the consequence: delete is permanent for a never-published course with no history, and a move to Deleted for anything that ever published. The Deleted drawer at the bottom lists each course with its URL, when it was deleted, and the state it holds; restoring a course that was open to learners reopens it immediately, and the drawer says so before you confirm. Leaving the list from the editor (⌘K → All courses) or after a delete returns you to the search, filters, and page you had.

Creating a course

New course is a row at the top of the Courses page (an empty academy opens it for you). It offers three doors, and each shows what it will do before it does it — the permanent URL the title mints (renaming later never changes it) and the structure about to be created:

  • Blank — an empty course. One click on Create your first lesson creates the first section for you and opens the editor with the title selected.
  • From template — pick a template (or arrive from Use template on the Templates page); the door lists what the new course starts with, what is copied and what isn't, and that the copy is independent.
  • Paste an outline — paste a markdown list and watch it parse as you type: headings become sections and list items become draft lessons; with no headings, indent lessons under their section instead. Outline-created lessons start empty, and Review & publish keeps them unselected until you write them.

Enter in the title creates the course. What you type survives a search or a reload in this tab until the course exists, and a failed create keeps the form with a Retry.

Lesson Library

Some lessons belong to every course — "Install the CLI", "Get an API key". The Lesson Library keeps one source of truth for those and shares it with consent, never behind your back.

Where it lives. Admin → Library: its lessons open in the same editor as any course lesson (same labs, same validation), inside the admin pages rather than a course workspace, and it is never visible to learners. New shared lesson creates the library itself the first time. The list is searchable, sortable (name, recently updated), and filterable by what maintenance needs — not used yet, update waiting, with local changes — 25 lessons a page.

What each shared lesson tells you. Its impact line under its name — "Used in 3 courses, 2 copies waiting for this version, 1 with local changes" — and the same summary heads the lesson page, with the copies on demand: course › section, whether the copy is in sync, waiting for the current version, or carrying local changes, and whether its learners have the draft ("Published", "Published, draft awaits publish", "Not published"). A copy in a template is labeled as such: it seeds new courses, learners never see it.

Note

"In sync" is a statement about drafts. A course's learners see nothing new until that course publishes, through its own release act.

Adding a shared lesson to a course makes a linked copy — a real lesson in the course, with its own URL, publish state, and history. Two doors: Add to course… on the library lesson (pick the course, then the section; a course without sections gets one created) and From library in a course's outline (search, an inline look at the content, a preview in a new tab, and "already in this course" when a copy is there — open it or add another). Add to library does the reverse: it promotes an existing lesson into the library and leaves the linked copy in its place; nothing breaks or moves.

When the library version changes, every linked copy shows update available — in the course outline, on the lesson, and on Review & publish. Pulling shows you the comparison first (current course draft → incoming library version, the direction the update writes), then replaces the copy's draft only. The draft it replaces is kept in your browser and offered back in the editor. If the draft or the library lesson changed while the comparison was open, nothing is replaced and you're asked to reload it: another author's edits are never applied or overwritten unseen. When a change is too large to show line by line, both versions are readable in full and the update still applies.

Titles stay independent. A copy's title is the course's own: renaming the library lesson never renames copies. When you pull an update, a checkbox can adopt the library title if you want it.

Diverging and re-linking. Editing a copy directly marks it diverged — the link pauses and updates stop being offered, because your edits win. You can compare with the library and re-adopt its version at any time. If you edit a copy until it matches the library again, it reads in sync; Mark as synchronized re-links it so the next library change shows up as an update rather than a divergence. Make independent keeps a copy — URL, content, history, learner records — and stops it following the library. It is also how you clear the way to delete a shared lesson: a library lesson can't be deleted while courses link to it.

Versions and deletion. Shared lessons never publish, so they have no publish history. Save a version keeps a snapshot you can diff against or restore (the lesson's Versions drawer; template lessons have the same button, for the same reason). A shared lesson with saved versions deletes to the library's Deleted drawer, restorable — while it sits there its copies read as ordinary lessons, and follow it again once restored — while one without deletes for good. Duplicate copies a shared lesson into the library with no links.

Important

Nothing is ever synced silently. Publishing the library never touches a course, and pulling an update is always your explicit choice, made with the comparison in front of you.

Course templates

Most academies' courses share a starting point — an intro section, a welcome lesson, a recap at the end. Build it once as a template and every new course can start from it. Templates live under Admin → Templates; they are edited like any course but are never visible to learners and can't be published. They exist to be copied.

A template is copied, never linked: a course created from one is independent from the first second. Editing the template changes what future courses start with; courses already created from it keep what they have. The same holds the other way — Save as template… on a course makes an independent copy under Templates, and later edits to the course don't reach it.

What a copy carries: every section and lesson with its current draft text (not a published version), the About page, the short description and course card (image, level, duration, tags), and the search & social preview. What it deliberately leaves behind: visibility and account access, certificate settings, the recommended next course, catalogs and featured, publish state and history. The new course starts as a draft with the defaults every new course gets.

The Templates page. Each row shows the name, its structure ("3 sections, 12 lessons, 2 empty"), when it was last edited, the description, and an Outline disclosure with the sections and lessons — enough to choose without opening the editor. Search by name or description; sort by Name (the same order the From-template picker uses) or Recently updated; 25 per page. Use template on a row opens the New course panel with that template picked — you name the course, see its URL, and create. The row's Actions menu opens the editor or Template settings, renames, duplicates (the copy stays a template, in this list), and deletes.

Creating a template. New template at the top offers Blank (an empty template, opened on "Create your first lesson") and From an existing course (pick the course, keep or change the suggested name — "Onboarding (template)", then "(template 2)" — and create). Save as template… in any course's settings is the same act from the other side.

Inside a template. The editor is the course workspace with template wording: the outline strip counts sections, lessons, empty lessons, and validation errors (never "published" or "draft" — templates don't release anything); Use this template sits at the bottom of the outline, with Template settings (the defaults new courses start with: the About page, card, and search preview copy into every course made from it) and All templates under it. There is no Access, no Completion, no history, and no analytics for a template.

Deleting a template. A template that has held lessons moves to the Deleted drawer at the bottom of the Templates page and can be restored exactly as it was; only a template that never held a lesson is removed for good. Courses created from a deleted template are unaffected. If a New course panel remembers a template that has since been deleted, it says so by name and asks you to pick another — it never quietly swaps in a different template.

Where the rest lives

Every other authoring surface has its own page:

  • Course settings — details, course card, visibility and access, completion.
  • Lab authoring — the lab block, every check type, Test lab, embedded labs.
  • Quizzes — the quiz block, question types, the Quizzes page.
  • Home & catalogs — the academy home, composing it yourself, catalogs, learning paths.
  • Content freshness — video transcripts, claims, and docs reviews.
  • Integrations & exports — courses as code (GitHub sync and publish on merge), webhooks, metric ingest, CSV.

On the roadmap

  • Full content export: your entire academy — markdown, assets, structure — in one click. Portability is a promise, not a feature flag; courses as code already gives repo-synced academies that portability today.

During the design-partner phase we author the first content with you — same-day. Staff seats are yours to hand out on Admin → Team (see Team); the academy is fully self-serve, and we stay a message away.