# How to build a learning management system in Bubble.io: Step-by-Step Guide

- Tool: Bubble
- Difficulty: Beginner
- Time required: 40-50 min
- Compatibility: Growth plan+ (video hosting and backend workflows benefit from higher plans)
- Last updated: March 2026

## TL;DR

A learning management system in Bubble requires Data Types for courses, modules, lessons, quizzes, enrollments, and progress tracking. This tutorial covers the complete LMS architecture from course creation to student enrollment, lesson delivery with video hosting, progress tracking, and certificate generation.

## Overview: Building a Learning Management System in Bubble

An LMS is a complex but highly valuable application — and one of Bubble's strongest use cases given its data-rich, multi-role nature. This tutorial walks you through building a complete LMS with course management, student enrollment, lesson delivery, progress tracking, quizzes, and certificates.

## Before you start

- A Bubble app on Growth plan or above
- Basic experience with Data Types, workflows, and Repeating Groups
- A video hosting service account (YouTube, Vimeo, or Loom)

## Step-by-step guide

### 1. Design the LMS data structure

Create these Data Types. Course: title (text), description (text), instructor (User), thumbnail (image), price (number), published (yes/no), category (Option Set). Module: course (Course), title (text), order (number). Lesson: module (Module), title (text), content (text — rich text HTML), video_url (text), order (number), duration_minutes (number). Enrollment: course (Course), student (User), enrolled_date (date), completed (yes/no), completion_date (date). LessonProgress: enrollment (Enrollment), lesson (Lesson), completed (yes/no), completed_date (date).

**Expected result:** A hierarchical data structure: Course → Modules → Lessons with enrollment and progress tracking.

### 2. Build the course catalog and enrollment flow

Create a courses page with a Repeating Group showing published courses. Each cell displays the thumbnail, title, instructor name, price, and an Enroll button. The Enroll button workflow creates an Enrollment record linking the current user to the course. For paid courses, redirect to Stripe Checkout first, then create the enrollment on successful payment via a webhook.

**Expected result:** Students can browse courses and enroll for free or paid access.

### 3. Create the course player with lesson navigation

Create a page called course-player with page type Course. Show a sidebar with all Modules (Repeating Group sorted by order) and nested Lessons within each module. The main content area shows the current lesson — embed the video using an HTML element with an iframe pointing to the video_url, and display the lesson content below using an HTML element for rich text. Use a custom state to track the current lesson.

> Pro tip: Use a Group with type Lesson to display the current lesson content, and update it by changing the data source when the user clicks a lesson in the sidebar.

**Expected result:** A course player with lesson navigation sidebar and main content area showing video and text.

### 4. Track lesson progress and course completion

When a student finishes a lesson (clicks a Mark as Complete button or watches the video to the end), create a LessonProgress record with completed = yes. After marking a lesson complete, check if all lessons in the course have progress records — if so, update the Enrollment to completed = yes and set the completion_date. Display progress visually using a progress bar (completed lessons / total lessons * 100).

**Expected result:** Student progress is tracked per lesson, and course completion is detected automatically.

### 5. Generate completion certificates

When an enrollment is marked as completed, create a certificate page that displays the student name, course title, completion date, and instructor name in a styled layout. Use the page as a printable certificate by styling it with a border, certificate-style fonts, and the course branding. Add a Print button that triggers window.print() via a Run JavaScript action. Alternatively, use a PDF generation plugin to create downloadable certificates.

**Expected result:** Students receive a printable or downloadable certificate upon course completion.

## Complete code example

File: `Workflow summary`

```text
LMS ARCHITECTURE SUMMARY
=========================

DATA TYPES:
  Course: title, description, instructor (User), thumbnail, price, published, category
  Module: course (Course), title, order (number)
  Lesson: module (Module), title, content (rich text), video_url, order, duration_minutes
  Enrollment: course (Course), student (User), enrolled_date, completed (yes/no), completion_date
  LessonProgress: enrollment (Enrollment), lesson (Lesson), completed, completed_date
  Certificate: enrollment (Enrollment), issued_date, certificate_id (text)

PAGES:
  courses — catalog with enrollment
  course-player (type: Course) — lesson viewer with sidebar nav
  certificate (type: Enrollment) — printable certificate
  instructor-dashboard — create/manage courses

ENROLLMENT WORKFLOW:
  Free: Create Enrollment (student=Current User, course, enrolled_date=now)
  Paid: Stripe Checkout → on success webhook → Create Enrollment

LESSON PROGRESS:
  Mark Complete button:
    1. Create LessonProgress (enrollment, lesson, completed=yes)
    2. Check: Search LessonProgress (enrollment, completed=yes) count
       = Search Lessons (course) count?
    3. If yes → Make Changes to Enrollment: completed=yes, completion_date=now
    4. If yes → Create Certificate

PROGRESS BAR:
  Width: (completed lessons / total lessons * 100)%
  Text: "X of Y lessons completed"

SIDEBAR NAVIGATION:
  RG Modules (course, sorted by order):
    Nested RG Lessons (module, sorted by order):
      Conditional: green checkmark when LessonProgress exists and completed=yes
      Click → Set state: current_lesson = This Lesson
```

## Common mistakes

- **Not nesting modules and lessons properly in the data structure** — Without a clear hierarchy (Course → Module → Lesson), navigation and progress tracking become very complex Fix: Use separate Data Types for Course, Module, and Lesson with parent references, and sort by an order field
- **Tracking completion at the course level only** — Without per-lesson progress, students cannot resume where they left off Fix: Create a LessonProgress record for each completed lesson so students can track their progress and resume
- **Hosting videos directly in Bubble** — Bubble's storage is limited and not optimized for video streaming — large video files will consume your storage quickly and load slowly Fix: Host videos on YouTube (unlisted), Vimeo, or Loom and embed them via iframe in an HTML element

## Best practices

- Use a hierarchical data structure: Course → Module → Lesson for clear organization
- Track progress at the lesson level for accurate completion tracking
- Host videos externally (YouTube, Vimeo) and embed via iframe to save storage
- Sort modules and lessons by an order field for consistent navigation
- Add a progress bar showing percentage completion on the course player page
- Generate certificates automatically when all lessons are marked complete

## Frequently asked questions

### How do I embed videos from YouTube or Vimeo?

Add an HTML element and paste an iframe tag: <iframe src="https://www.youtube.com/embed/VIDEO_ID" width="100%" height="400" frameborder="0" allowfullscreen></iframe>. Make the src dynamic using the lesson's video_url field.

### Can I restrict access to paid course content?

Yes. On the course player page, check if the current user has an Enrollment for the course. If not, redirect to the enrollment page or show a paywall message.

### How do I handle multiple instructors?

Add an instructors field (list of Users) to the Course Data Type. Use Privacy Rules to give instructors edit access only to their own courses.

### Can students leave reviews?

Yes. Create a Review Data Type with course (Course), student (User), rating (number), comment (text). Display reviews on the course catalog page.

### Is Bubble suitable for a large-scale LMS?

For small to medium platforms (hundreds to low thousands of students), yes. For enterprise LMS with thousands of concurrent users, advanced reporting, and SCORM compliance, you may need hybrid architecture. RapidDev can help build and scale LMS platforms in Bubble.

### Can I add downloadable resources to lessons?

Yes. Add a resources field (file, list) to the Lesson Data Type. Display download links below the lesson content using a Repeating Group of the resource list.

---

Source: https://www.rapidevelopers.com/bubble-tutorial/build-a-learning-management-system-in-bubble
© RapidDev — https://www.rapidevelopers.com/bubble-tutorial/build-a-learning-management-system-in-bubble
