justinholtweb/craft-diploma

LMS plugin for Craft CMS 5. Create courses, lessons, quizzes, track progress, and issue certificates.

Maintainers

Package info

github.com/justinholtweb/craft-diploma

Type:craft-plugin

pkg:composer/justinholtweb/craft-diploma

Statistics

Installs: 16

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

5.0.0 2026-03-24 20:28 UTC

This package is auto-updated.

Last update: 2026-03-24 20:32:02 UTC


README

Create and manage online courses, lessons, quizzes, and certificates directly in Craft CMS. Diploma brings the essential features of a learning management system into the Craft ecosystem -- structured courses with ordered lessons, auto-graded quizzes, progress tracking, and verifiable certificates.

Editions

Feature Lite ($99/yr) Pro ($149/yr)
Courses & Lessons
Quizzes (MC, T/F, short answer, matching)
Progress tracking
Basic certificates (HTML)
Dashboard widgets
Drip content (lesson scheduling) --
PDF certificates (DOMPDF) --
Craft Commerce integration --
Headcount membership gating --
Advanced analytics/reporting --
Course bundles --

Requirements

  • Craft CMS 5.3.0+
  • PHP 8.2+
  • DOMPDF (optional, Pro PDF certificates)

Installation

composer require justinholtweb/craft-diploma
php craft plugin/install diploma

Configuration

All settings are available in the control panel under Diploma > Settings, or override them via config/diploma.php:

<?php

return [
    'enableCertificates' => true,
    'certificateTemplatePath' => '',
    'autoEnrollOnPurchase' => true,
    'autoIssueCertificate' => true,
    'requirePassingQuiz' => false,
    'defaultPassingScore' => 70,
];

Usage

Courses & Lessons

Courses and Lessons are custom element types with full field layout support. Create and manage them from Diploma > Courses in the CP. Lessons are nested under their parent course and support drag-and-drop reordering.

Each course has:

  • Status -- Draft, Published, or Archived
  • Difficulty -- Beginner, Intermediate, or Advanced
  • Estimated duration -- in minutes
  • Enrollment limit -- optional cap on enrollments
  • Passing score -- percentage required for completion

Each lesson has:

  • Type -- Text, Video, or Mixed
  • Sort order -- drag to reorder within a course
  • Prerequisite -- require a previous lesson be completed first
  • Free preview -- allow unenrolled users to access the lesson
  • Drip delay (Pro) -- days after enrollment to unlock

Quizzes

Quizzes are a separate element type managed under Diploma > Quizzes. Each quiz can be linked to a course, a specific lesson, or both. Supported question types:

  • Multiple Choice -- one correct answer from several options
  • True/False -- binary choice
  • Short Answer -- text response, graded via exact match
  • Matching -- pair items together

Quiz settings include passing score, time limit, max attempts, question randomization, and questions-per-attempt limits.

Enrollments

Manage enrollments from Diploma > Enrollments. Users can be enrolled manually via the CP, or automatically via:

  • Self-enrollment (front-end form)
  • Craft Commerce purchase (Pro)
  • Headcount subscription (Pro)

Enrollment statuses: Active, Completed, Cancelled, Expired.

Certificates

When a user completes a course (and passes any required quizzes), a certificate is automatically issued with a unique verification code. Certificates can be:

  • Viewed at a public verification URL
  • Downloaded as PDF (Pro, requires DOMPDF)
  • Customized via settings (title, body text, or a custom Twig template)

Template Reference

Template Variable: craft.diploma

{# Course queries (returns element queries) #}
{% set courses = craft.diploma.courses().status('published').all() %}
{% set course = craft.diploma.courses().slug('intro-to-php').one() %}

{# Lesson queries #}
{% set lessons = craft.diploma.lessons().courseId(course.id).all() %}

{# Enrollment & progress #}
{% set enrollment = craft.diploma.getEnrollment(course, currentUser) %}
{% set progress = craft.diploma.getCourseProgress(course, currentUser) %}
{{ progress.percentage }}%
{{ progress.completedLessons }} / {{ progress.totalLessons }}

{# Quiz results #}
{% set attempts = craft.diploma.getQuizAttempts(quiz, currentUser) %}
{% set bestScore = craft.diploma.getBestScore(quiz, currentUser) %}

{# Certificate #}
{% set certificate = craft.diploma.getCertificate(course, currentUser) %}
{% if certificate %}
    <a href="{{ certificate.verifyUrl }}">View Certificate</a>
{% endif %}

{# Access checks #}
{% if craft.diploma.isEnrolled(course, currentUser) %}
{% if craft.diploma.isLessonUnlocked(lesson, currentUser) %}
{% if craft.diploma.hasCompleted(course, currentUser) %}

Twig Filters

{# Duration in minutes to human-readable: 90 -> "1h 30m" #}
{{ course.estimatedDuration|diplomaDuration }}

{# Float or percentage formatting: 0.75 -> "75%" #}
{{ progress.percentage|diplomaProgress }}

{# Seconds to human-readable: 3661 -> "1h 1m 1s" #}
{{ progress.totalTimeSpent|diplomaTimeSpent }}

Front-end Forms

Self-enrollment:

<form method="post">
    {{ csrfInput() }}
    {{ actionInput('diploma/progress/enroll') }}
    {{ redirectInput('/courses/' ~ course.slug) }}
    <input type="hidden" name="courseId" value="{{ course.id }}">
    <button type="submit">Enroll Now</button>
</form>

Mark lesson complete (AJAX):

<form method="post" class="diploma-complete-lesson">
    {{ csrfInput() }}
    {{ actionInput('diploma/progress/complete-lesson') }}
    <input type="hidden" name="lessonId" value="{{ lesson.id }}">
    <button type="submit">Mark Complete</button>
</form>

Submit a quiz:

<form method="post">
    {{ csrfInput() }}
    {{ actionInput('diploma/quiz-taking/submit') }}
    {{ redirectInput('/courses/' ~ course.slug ~ '/quiz-results') }}
    <input type="hidden" name="quizId" value="{{ quiz.id }}">

    {% for question in quiz.getQuestions() %}
        <fieldset>
            <legend>{{ question.questionText }}</legend>
            {% for answer in question.answers %}
                <label>
                    <input type="radio"
                           name="responses[{{ question.id }}][answerId]"
                           value="{{ answer.id }}">
                    {{ answer.answerText }}
                </label>
            {% endfor %}
        </fieldset>
    {% endfor %}

    <button type="submit">Submit Quiz</button>
</form>

Front-end API

All endpoints require a logged-in user.

Method Endpoint Description
POST /diploma/api/enroll Enroll current user in a course
POST /diploma/api/complete-lesson Mark a lesson complete
POST /diploma/api/submit-quiz Submit quiz answers for grading
GET /diploma/api/progress/<courseId> Get progress for a course
GET /diploma/certificate/verify/<code> Public certificate verification
GET /diploma/certificate/download/<code> Download PDF certificate (Pro)

Commerce Integration (Pro)

Link courses to Craft Commerce products. When an order completes, buyers are automatically enrolled in the associated courses.

  1. Navigate to Diploma > Settings > Commerce
  2. Add a diplomaCourses relation field to your Commerce product field layout
  3. When a customer purchases a product with linked courses, enrollment happens automatically

Headcount Integration (Pro)

Gate course access behind Headcount membership plans.

  1. Navigate to Diploma > Settings > Headcount
  2. Map Headcount plans to Diploma courses
  3. When a user subscribes to a plan, they are automatically enrolled
  4. When a subscription expires or is cancelled, the enrollment is marked as expired

Permissions

Permission Description
diploma:accessPlugin Access the Diploma CP section
diploma:manageCourses Create and edit courses and lessons
diploma:deleteCourses Delete courses (nested under manageCourses)
diploma:manageQuizzes Create and edit quizzes
diploma:deleteQuizzes Delete quizzes (nested under manageQuizzes)
diploma:manageEnrollments Manage user enrollments
diploma:viewCertificates View issued certificates
diploma:viewAnalytics View analytics dashboard (Pro)
diploma:manageSettings Modify plugin settings

Database Tables

Table Purpose
diploma_courses Course element content (FK to elements)
diploma_lessons Lesson element content (FK to elements)
diploma_quizzes Quiz element content (FK to elements)
diploma_questions Quiz questions
diploma_answers Answer options for questions
diploma_enrollments User-course enrollments
diploma_progress Per-lesson completion tracking
diploma_quiz_attempts Quiz attempt records
diploma_quiz_responses Individual question responses
diploma_certificates Issued certificates with verification codes
diploma_drip_schedules Drip content timing (Pro)

Support