.ef page templates. See Courses for setup and enrollment.
Backend functions
All functions are available in<script scope="backend"> blocks and in .ef files.
Who gets full lessons. Every function answers for the signed-in customer. A student with
active access to a course gets its lessons in full. Anyone else gets the outline: titles, ids,
type and order, with
locked: true on each lesson. A draft course is hidden from listings and
from visitors who are not enrolled in it (an enrolled student and a preview still see it). See
Courses on your own pages.getCourses(opts?)
Returns all courses for the brand, each with is_assigned and started for the current customer.
sort supports:
enrollment_oldest_unfinished_first(default)title_asctitle_descpreferred(when used, passpreferredOrder: ['slug-or-title', ...])
getCategoriesWithCourses(opts?)
Same as getCourses but grouped by category. Returns an array of { category_slug, category_name, courses[] }.
getCoursesByCategorySlug(opts)
Returns courses in a single category. Useful for “Daily practices” or “Advanced” sections on a dashboard.
getCoursesForCustomer(opts?)
Returns only courses the current customer is enrolled in (same as getCourses({ assignedOnly: true })). Returns [] if no session or no enrollments.
getCourseForCustomer(opts)
Returns a single course with enrollment check — is_assigned and started are set for the current customer. Also returns the full modules[] and contents[] tree, plus progress_percent and completed_content_ids. Use this on the course detail page.
null if no course with that slug exists.
getCourseBySlug(slug)
Returns a single course by slug. Use it for course metadata and the outline, for example on a sales or teaser page. A visitor without access gets the outline (lessons with locked: true); a student with access gets the full lessons. A draft course returns null for anyone not enrolled in it.
getCourseLessons(opts)
Returns every lesson of a course in order (module order, then lesson order) as one flat list. Useful for an app-style lesson list without the course portal.
[] unless the student is enrolled in it.
getCourseLesson(opts)
Returns the resolved current lesson payload for lesson-player pages, including currentLesson, prevLessonUrl, nextLessonUrl, and progress stats.
course/{course}/module/{module}/lesson/{lesson}, getCourseLesson() finds the lesson the visitor opened. In a template, the URL segments are also on request.route_params (request.route_params.course, .module, .lesson).
getOrderCourses()
Returns the courses the visitor’s latest order in this session gives access to, as a list of { title, url }. Use it on your own thank-you page to send a course buyer straight to the course. It returns [] when the session has no order or the order unlocks no course.
/course/{slug} on the domain the buyer is on. A buyer who is not signed in yet is asked to sign in first. When a project has no thank-you page, the built-in one shows the same links.
getCompletedLessonsCount(opts?)
Returns the total number of completed lessons across all enrolled courses, or for a specific course when a slug is provided.
0 if the customer has no enrollments or no completed lessons.
getCourseProgress(opts?)
Returns a structured progress summary with completed/total lesson counts per course and an overall total. Useful for dashboards.
request.route_params
For dynamic slug pages (e.g. guided-course/{course}.ef), the slug is available via request.route_params.course.
Course list page
Show all courses, sorted so enrolled courses appear first. Useis_assigned and started to drive the CTA label.
With category tabs
Course detail page
Resolve the slug from the URL, check enrollment withgetCourseForCustomer, and gate content.
Auth wrapper for course pages
Use the auth wrapper pattern (@extends) so the login check and common data load happen once across all course pages:
Checking access to a specific product
If you want to gate course access by checking whether the customer bought a specific product (rather than relying on enrollment), you can cross-reference withgetOrders():
Prefer the enrollment-based check (
getCourseForCustomer) — it is more reliable and does not require iterating order history. Use the order-history fallback only if enrollment is not being set up via product linking.Lesson types and content
Each lesson inmod.contents has a type field:
Additional fields on each lesson:
Progress API
All progress routes are prefixed with/api/course/courses/:courseId/. Authentication is via the active course session (cookie). courseId, moduleId, and contentId are numeric IDs.
Mark a lesson complete
{ success: true, message, stats: { completed_lessons, total_lessons, completion_percent } }.
Save general progress
courseId, moduleId, and contentId go in the URL for the lesson-specific routes. The general saveProgress route keeps them in the body — useful when you want to upsert a progress record with a specific completed value (e.g. un-completing).
Save video position
Get all progress for a course
Get progress for a single lesson
Get course stats
course.completed_content_ids. Use this array to mark lessons visually on load, then update the UI optimistically when the API call succeeds.