Skip to content
Article Courses, Teachers & Schools (LMS)
☀️ Light Clean and bright
📜 Sepia Warm and vintage
🌤️ Light Gray Subtle and neutral
🔵 Light Blue Calm and serene
🌙 Dark Easy on the eyes
🌑 Dark Gray Deep and modern
🔷 Dark Blue Professional and sleek
🌿 Forest Calm and natural

Courses, Teachers & Schools

A file-based learning platform — no database. Courses, teachers, and schools are Markdown pages (create/edit/delete them with the normal docs editor); lessons are Markdown pages too; enrollments and reviews are JSON on disk. Payments reuse the shop's products/_payments.md.

Image paths resolve against the asset base (public/).


Courses

Courses index

courses.md with courses_dir: courses renders the course grid.

A course page

One file per course in courses/. Body = the Outline tab intro.

KeyDescription
titleCourse name.
levelBeginner / Intermediate / Advanced — shown as a badge.
priceNumber. 0/omitted = free (enroll is instant, no payment).
currencySymbol. Default $.
imageHero + card image.
summaryLead line (alias description); also the card blurb.
schoolOwning school's file basename (links course to school).
teacherTeacher's file basename (links to the profile).
teacher_bioShort bio shown in the Teacher tab.
start_dateShown in the right panel.
end_dateShown in the right panel.
objectivesLabel list -> the Objectives tab ("what you'll learn").
outline`Title\lesson-ref\note list -> the numbered, linkable **Outline** (alias lessons`). See Lessons.
payment_methodsAllowed method ids. Empty = all configured.
relatedCourse basenames to feature.
coursetrue to force course rendering outside courses/.
---
title: Intro to Web Development
level: Beginner
price: 40
currency: $
school: sepodesk-academy
teacher: jane-okoro
teacher_bio: Jane has taught web dev for 8 years.
start_date: 2026-09-01
end_date: 2026-11-30
objectives: [Write semantic HTML, Style with CSS, Deploy a site]
outline:
  [
    HTML foundations|html-foundations|Structure,
    CSS layout|css-layout|Flexbox & grid,
  ]
payment_methods: [bank, paypal]
related: [advanced-css]
---

The course page has tabs (Outline / Objectives / Teacher / Reviews), star reviews, related courses, and a right panel showing level, enrolled count, start/end dates, price, and an Enroll / Enroll & pay button.


Lessons

Each course keeps its lessons in a folder of the same name next to the course file. The course file holds the meta; the folder holds the content:

content/pages/courses/intro-web-dev.md         <- the course (meta, outline)
content/pages/courses/intro-web-dev/           <- its lessons folder
    html-foundations.md
    css-layout.md
    js-basics.md

A lesson page is an ordinary Markdown file — give it a title and an order:

---
title: HTML foundations
summary: Structure and semantics
order: 1
---
# HTML foundations
Your lesson content...

Adding a lesson = dropping a new .md file into the course's folder. There is no separate registration step.

Linking lessons from the outline

Each outline entry is Lesson title | lesson-ref | note, where lesson-ref is the lesson file's basename inside the course folder (no .md, no path):

outline:
  [
    HTML foundations|html-foundations|Structure and semantics,
    CSS layout|css-layout|Flexbox and grid,
    JavaScript basics|js-basics|Variables to the DOM,
  ]
  • Provide lesson-ref and the outline item becomes a link to that lesson.
  • Omit lesson-ref (just Title or Title|note) and it renders as plain, unlinked text — useful for section headers.
  • Omit outline entirely and the course auto-lists every lesson in its folder, ordered by each lesson's order front matter.

Locking lessons

Lesson access is gated by enrollment and payment. The rule:

CourseA viewer may open a lesson when...
Freethey have an active enrollment.
Paidthey have a paid enrollment.
Anythey are an admin (canEdit) — always, for authoring.

A pending enrollment (a manual bank/cash payment not yet confirmed) stays locked until an admin marks it paid.

This is enforced in two places:

  • On the outline (cosmetic): unlocked lessons appear as links; locked ones are greyed with a lock icon and aren't clickable.
  • On the lesson page (the real gate): the page checks the parent course's price and the viewer's enrollment status server-side and refuses to render the lesson body unless access is granted. A locked visitor sees a clear message (log in / enroll / complete payment / payment pending) and a button back to the course. Because the gate lives on the page, it holds even if a lesson URL is shared or guessed.

Unlocked lessons render with a breadcrumb back to the course and prev/next navigation across the folder.

The lock controls page rendering. If a lesson embeds a downloadable file or video by a direct public/ URL, that asset is not itself gated — stream protected media through an auth-checked route (like the shop's /shop/download) instead of linking it directly.


Enrollment

Clicking Enroll opens a form collecting the student's full name, email, national ID, gender, location, and date of birth, plus a payment method for paid courses. On submit:

  • Free course -> enrolled with status active.
  • Paid, instant method -> paid. Paid, manual/link -> pending with instructions until an admin marks it paid.

One enrollment per user per course. The email captured here is how you collect student emails.

The enrollment record holds personal data (national ID, DOB, gender). Keep content/lms/ outside the public web root.

My Courses

my_courses: true -> the signed-in student's own enrollments (course, level, amount, status).

Enrolled Students (admin)

enrolled_students: true (admin-only) -> every enrollment across all courses, with Activate / Mark paid / Remove, CSV + PDF export, and 10-per-page pagination. This view exposes student PII — admins only.


Teachers

Index / profile

teachers.md with teachers_dir: teachers; profiles live in teachers/.

KeyDescription
titleTeacher name.
roleTitle / main subject (alias subject).
avatarPhoto (alias image).
locationShown as a chip.
emailmailto: chip.
subjectsLabel list of tags.
socials`Label\URL list (alias links`).
teacher_pagetrue to render outside teachers/.

The profile lists every course whose teacher: matches this file's basename.


Schools

Index / school page

schools.md with schools_dir: schools; schools live in schools/.

KeyDescription
titleSchool name.
locationChip.
websiteLink chip.
emailmailto: chip.
logo / coverLogo + banner (gradient fallback).
descriptionIntro / card blurb.
accreditations`Label\Note list (alias affiliations`).
school_pagetrue to render outside schools/.

The school page lists every course whose school: matches its basename.


Data on disk (no database)

DataLocation
Course metacontent/pages/courses/<course>.md
Lessonscontent/pages/courses/<course>/<lesson>.md
Enrollmentscontent/lms/enrollments/<course>.enroll.json
Reviewscontent/lms/reviews/<course>.reviews.json
Paymentscontent/pages/products/_payments.md (shared)

API summary

EndpointAuthPurpose
POST /api/v2/lms/enrollsigned-inEnroll (with student data).
POST /api/v2/lms/reviewssigned-inAdd/replace a course review.
POST /api/v2/lms/enroll/statusadminActivate / mark paid.
POST /api/v2/lms/enroll/removeadminRemove a student.

Reachability

Add courses, teachers, schools to the sidebar exclusion list and their *_dir keys to the aggregate keys. Excluding courses also hides every lesson under courses/<course>/ from the sidebar. Surface pages from _nav.md:

- [Courses](/docs/courses)
- [Teachers](/docs/teachers)
- [Schools](/docs/schools)
- [My Courses](/docs/my-courses)