PHPackages                             particle-academy/laravel-courses - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. particle-academy/laravel-courses

ActiveLibrary

particle-academy/laravel-courses
================================

Laravel package for selling and serving educational curriculums, courses, tests, and certificates.

v0.1.0(today)00MITPHPPHP ^8.3CI passing

Since Aug 2Pushed todayCompare

[ Source](https://github.com/Particle-Academy/laravel-courses)[ Packagist](https://packagist.org/packages/particle-academy/laravel-courses)[ RSS](/packages/particle-academy-laravel-courses/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (9)Versions (2)Used By (0)

particle-academy/laravel-courses
================================

[](#particle-academylaravel-courses)

Laravel package for selling and serving educational curriculums, courses, tests, and certificates. API-only — bring your own UI (or use the [fancy-courses React kit](#fancy-courses-react-uX) shipped alongside).

Install
-------

[](#install)

```
composer require particle-academy/laravel-courses
php artisan migrate
```

The package's `CoursesServiceProvider` auto-discovers; migrations and routes load automatically.

Publish what you want to override:

```
php artisan vendor:publish --tag=laravel-courses-config       # config/laravel-courses.php
php artisan vendor:publish --tag=laravel-courses-views        # resources/views/vendor/laravel-courses/
php artisan vendor:publish --tag=laravel-courses-migrations   # if you need to customize the schema
```

Configure
---------

[](#configure)

`config/laravel-courses.php`:

```
'user_model'   => App\Models\User::class,           // host's user model
'routes' => [
    'enabled'    => true,
    'prefix'     => 'api/courses',
    'middleware' => ['api', 'auth:sanctum'],         // gate however you authenticate
],
'defaults' => [
    'passing_score' => 70,
    'max_attempts'  => null,
],
'certificates' => [
    'default_view'   => 'laravel-courses::certificates.default',
    'storage_disk'   => env('LARAVEL_COURSES_CERT_DISK', 'local'),
    'storage_path'   => 'certificates',
    'paper'          => 'letter',
    'orientation'    => 'landscape',
    'number_prefix'  => env('LARAVEL_COURSES_CERT_PREFIX', 'CERT'),
    'number_format'  => '{prefix}-{year}-{random}',
    'number_random_length' => 6,
],
```

Domain
------

[](#domain)

```
Curriculum ─< curriculum_course >─ Course ─< Module ─< Lesson
                                   Course ─< Test ─< Question ─< QuestionOption
                                   Module ─< Test
                                   Lesson ─< Test

Enrollment morphs to Curriculum OR Course
Enrollment ─< LessonCompletion
Enrollment ─< TestAttempt ─< AttemptAnswer
Enrollment ─── Certificate ─── CertificateTemplate

```

- **Tests can attach to a course, a module, or a single lesson.** Use whichever level fits the assessment.
- **Enrollments are polymorphic.** A learner can enroll in either a `Curriculum` (the whole program) or a single `Course`.
- **Certificates are signed by both a random `verification_code` (URL-safe) and a human-readable `certificate_number`** (`{prefix}-{year}-{random}` by default). Either resolves the public `verify` endpoint.

### Slugs

[](#slugs)

`Curriculum`, `Course`, `Test`, `CertificateTemplate`, `Module` and `Lesson` fill in their own `slug` when you do not supply one — derived from the title, with a `-2`, `-3` suffix on collision. `modules` and `lessons` are unique per course; the rest are unique globally.

```
Course::create(['title' => 'Patrol Basics']);   // slug: patrol-basics
Course::create(['title' => 'Patrol Basics']);   // slug: patrol-basics-2
```

Pass a `slug` explicitly to override. **Create-only** — an existing slug is never rewritten when a title changes, because a published URL that silently moves is worse than an out-of-date one.

Endpoints
---------

[](#endpoints)

All routes mount under the configured prefix (default `/api/courses`).

### Authoring (CRUD)

[](#authoring-crud)

MethodPathNotes`GET / POST / GET-:slug / PATCH-:slug / DELETE-:slug``curriculums`slug-bound`POST``curriculums/{slug}/courses`attach a course (sort\_order, is\_required)`DELETE``curriculums/{slug}/courses/{slug}`detach`*``courses`slug-bound; also accepts price, currency, highlights, is\_required, hours`*``courses/{slug}/modules`, `courses/{slug}/lessons`nested`*``tests`, `tests/{slug}/questions`, `questions/{id}/options`nested`*``certificate-templates`one row should be `is_default: true`### Learner flow

[](#learner-flow)

MethodPathNotes`GET``enrollments`current learner's enrollments`POST``enrollments`enroll: `target_kind`, `target_slug`/`target_id`, optional `metadata``GET``enrollments/{id}`includes progress summary`DELETE``enrollments/{id}`drops the enrollment`POST``enrollments/{id}/lessons/{lesson}/complete`marks lesson done`POST``enrollments/{id}/tests/{test}/attempts`starts an attempt`GET``attempts/{id}`full attempt with answers`POST``attempts/{id}/submit`grades and finalizes`POST``enrollments/{id}/certificate`issues once enrollment is fully complete`GET``certificates/{id}`learner's own certificate`GET``certificates/{id}/pdf`downloads the rendered PDF### Admin / public

[](#admin--public)

MethodPathNotes`POST``admin/completions`short-circuit: enroll + complete + issue cert in one call (`user_id`, `target_kind`, `target_slug`/`target_id`, `issued_by_user_id`, `notes`, `completed_at`, `expires_at`)`POST``certificates/{id}/revoke`revoke with optional `reason``GET``verify/{code}`public verify by `verification_code` OR `certificate_number`. Returns `valid: false` if revoked.Learner resolution
------------------

[](#learner-resolution)

`LearnerResolver` resolves the learner id for any request, in this order:

1. `$request->user()` (set by the host's auth middleware) — preferred.
2. `user_id` in the request body or `X-Learner-Id` header — useful for server-to-server callers or tests. Disable with `config('laravel-courses.allow_input_user_id', false)`.

Events
------

[](#events)

Listen from your host app to wire audit logs, broadcasts, notifications:

```
\ParticleAcademy\LaravelCourses\Events\LearnerEnrolled
\ParticleAcademy\LaravelCourses\Events\LessonCompleted
\ParticleAcademy\LaravelCourses\Events\TestAttemptFinished
\ParticleAcademy\LaravelCourses\Events\EnrollmentCompleted
\ParticleAcademy\LaravelCourses\Events\CertificateIssued
\ParticleAcademy\LaravelCourses\Events\CertificateRevoked
```

Certificate templates
---------------------

[](#certificate-templates)

Two modes per `CertificateTemplate` row:

- **Blade view**: set `blade_view` (e.g. `vendor.acme.cert`) — that view is rendered with the variables below.
- **Inline HTML**: set `html` + optional `css` — `{{ var }}` tokens are replaced with sanitized values.

Variables available to either mode:

```
recipient, recipientName, programName, programDetail, issuedAt, issuer,
verificationCode, certificate, enrollment

```

The default template ships at `resources/views/certificates/default.blade.php`. Publish + edit it to brand certificates.

Fancy-Courses React UX
----------------------

[](#fancy-courses-react-ux)

A companion React kit lives at `resources/js/packages/fancy-courses/` in the sandbox app. It's the in-tree dev checkout of the future `@particle-academy/fancy-courses` npm package — built on `@particle-academy/react-fancy`, talks to this API. To extract: lift the whole folder into its own repo and `npm publish`. The peer deps are already declared in its `package.json`.

Components:

- `CurriculumOverview` — list + enroll + progress per course
- `CoursePlayer` — lesson + test sidebar; main pane swaps modes
- `LessonView` — html / video / mixed lesson rendering
- `TestRunner` + `QuestionRenderer` — adapts to all four question types
- `CertificateView` — display + download

The `CoursesClient` (axios) covers the learner flow. Pass `learnerId` in its options to send `X-Learner-Id`.

Tests
-----

[](#tests)

Inside the host app (which already has the package autoloaded via path repo):

```
php artisan test --filter=Courses
```

The shipped tests cover the full learner flow (enroll → complete lesson → pass test → issue certificate → public verify) and the revocation cycle. Factories for every model live under `database/factories/`.

For a quick interactive smoke:

```
php artisan tinker --execute="require 'packages/laravel-courses/tests/smoke-flow.php';"
```

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/461446?v=4)[Wish Born](/maintainers/wishborn)[@wishborn](https://github.com/wishborn)

---

Top Contributors

[![wishborn](https://avatars.githubusercontent.com/u/461446?v=4)](https://github.com/wishborn "wishborn (11 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/particle-academy-laravel-courses/health.svg)

```
[![Health](https://phpackages.com/badges/particle-academy-laravel-courses/health.svg)](https://phpackages.com/packages/particle-academy-laravel-courses)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.4M352](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

78727.1M205](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

58174.6k18](/packages/api-platform-laravel)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9872.4M142](/packages/roots-acorn)[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1239.7k25](/packages/fleetbase-core-api)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45844.8k1](/packages/pressbooks-pressbooks)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
