tapp/filament-lms
Filament LMS brings a complete learning management system to your Filament admin panel. Manage courses, lessons, modules, students, enrollments and progress from one familiar Laravel interface—ideal for building online course platforms without extra tooling.
Install the package and publish its assets before touching any models:
composer require tapp/filament-lms
php artisan vendor:publish --tag="filament-lms-config"
php artisan vendor:publish --tag="filament-lms-migrations"
php artisan migrate
Register the plugin in the panel provider that should host the LMS admin surface:
use Tapp\FilamentLms\FilamentLmsPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(FilamentLmsPlugin::make());
}
Where to look first, in this order:
README.md for the canonical install snippet and any required User contract/trait.config/filament-lms.php after publishing — this tells you which models, disks, and route prefixes are swappable.database/migrations/ — the schema is the real API. Course → lesson → enrollment → progress is the mental model you need.src/Models/ and src/Filament/Resources/ — resources reveal the expected relationships and authorization behaviour.src/FilamentLmsPlugin.php — every ->...() method there is an extension point.First use case to validate the install: log into the panel, create a Course, attach two Lessons, enrol your own user, and mark one lesson complete. If the progress percentage updates in the admin table, the package is wired correctly and you can start integrating it into your domain.
Separate admin and learner surfaces. Keep the package's resources in an admin panel and build learner-facing pages yourself (or in a second panel). The package's Filament resources are written for management CRUD; trying to reuse them as a student UI fights the defaults.
Treat the models as your domain layer. For custom frontend work, depend on the package models directly rather than the Filament resources:
$course->lessons()->orderBy('position')->get();
$enrollment = $user->enrollments()->firstOrCreate(['course_id' => $course->id]);
Wrap enrolment and completion in your own actions/services so you have one place to fire notifications, award certificates, or sync to a CRM.
Use policies, not resource-level conditionals. Publish/generate policies for the LMS models and register them normally. Filament will respect viewAny, create, update on resources, and you get the same rules for free on any custom pages.
Eager-load for list screens. Lesson and enrolment tables typically traverse course, user, and progress relations. Add ->modifyQueryUsing(fn ($query) => $query->with(['course', 'user'])) on tables, or override the resource's getEloquentQuery().
Customise via schema/table callbacks where available. Most Tapp plugins expose ->form(fn (Form $form) => ...) or resource-level static methods. Prefer these over forking the resource classes — you keep upstream fixes.
Multi-tenancy. If your panel uses Filament tenancy, confirm the LMS models carry team_id/tenant_id (check migrations). Add a global scope in a model extension if not, otherwise courses leak across tenants.
Testing. Follow the repo's own conventions — Pest with uses(RefreshDatabase::class), test()/expect(), and beforeEach(). Build a small factory helper that creates a course with lessons plus an enrolled user; nearly every LMS test starts there.
Run the tooling the maintainers run:
./vendor/bin/pint
./vendor/bin/phpstan analyse
Do this before every PR; the CI expects clean style and static analysis.
migrate without publishing migrations silently gives you no LMS tables. Publish, review the migration filenames, then migrate.config/filament-lms.php run php artisan config:clear (and filament:optimize-clear if you use Filament's optimisation commands). A stale cached config is the most common "my disk/model override isn't applied" cause.composer.json constraint against your Filament version. A Filament 3 → 4 jump in a plugin this small can lag; test panel registration after any Filament upgrade.firstOrCreate/upsert, not a create, or you'll get duplicate progress rows and inflated percentages.Gate::authorize('view', $course) calls, especially for lesson content and downloads.Storage::disk(...)->url() rather than hardcoded paths.lesson_id.resolveRouteBinding.php artisan route:list | grep lms, php artisan about, verify the plugin is in the panel's ->plugin() chain, and dd() inside the resource's getEloquentQuery() to confirm scoping. If resources don't appear in navigation, it's almost always plugin registration or policy denial.How can I help you explore Laravel packages today?