An opinionated LMS plugin for Filament containing a user facing LMS panel and Resources for an existing admin panel
| Filament | Filament LMS | Documentation |
|---|---|---|
| 4.x/5.x | 4.x | Current |
| 3.x | 1.x | Check the docs |
"minimum-stability": "dev""repositories": {
"tapp/filament-lms": {
"type": "vcs",
"url": "https://github.com/tappnetwork/filament-lms"
},
"tapp/filament-form-builder": {
"type": "vcs",
"url": "https://github.com/tappnetwork/filament-form-builder"
}
},or
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/tappnetwork/filament-lms"
},
{
"type": "vcs",
"url": "https://github.com/TappNetwork/Filament-Form-Builder"
}
],
}composer require tapp/filament-lms:"^4.0"Make sure that the Filament Form Builder migrations are published before.
Then publish the migrations:
php artisan vendor:publish --provider="Tapp\FilamentLms\FilamentLmsServiceProvider"Warning
If you are using multi-tenancy, please see the "Multi-Tenancy Support" section below before running migrations.
run migrations after publishing
This will create resources that allow admin to manage course material.
class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
\Tapp\FilamentLms\Lms::make(),
])
}
}This package uses Tailwind CSS classes in its Blade views. The configuration differs between Tailwind v3 and v4:
- Install Tailwind CSS in your project (if not already installed):
npm install -D tailwindcss
npx tailwindcss init- Configure Tailwind to include the package's views in your
tailwind.config.js:
module.exports = {
content: [
// ... your existing content paths
'./vendor/tapp/filament-lms/resources/views/**/*.blade.php',
'./vendor/tapp/filament-form-builder/resources/views/**/*.blade.php',
],
// ... rest of your config
}- Include the package CSS in your main CSS file (e.g.,
resources/css/app.css):
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
/* Import the LMS package styles */
@import '../../vendor/tapp/filament-lms/dist/filament-lms.css';- Build your CSS to include both Tailwind and the package styles:
npm run build- Install Tailwind CSS v4 in your project:
npm install -D @tailwindcss/vite@next- Include the package CSS in your main CSS file (e.g.,
resources/css/app.cssorresources/css/theme.css):
@import "tailwindcss";
/* Import the LMS package styles */
@import '../../vendor/tapp/filament-lms/dist/filament-lms.css';- Build your CSS to include both Tailwind and the package styles:
npm run buildNote: The package provides its own CSS for component-specific styling, while using Tailwind classes in views for layout and utilities. This approach ensures no dependency conflicts while maintaining the benefits of Tailwind CSS.
For more detailed Tailwind CSS configuration options, refer to the official Tailwind CSS documentation.
- create the directory {project}/packages
- from within the packages directory, clone this repo
- (if necessary) add a type:path repository to project composer.json
Filament LMS includes built-in support for multi-tenancy, allowing you to scope courses, lessons, steps, and all learning materials to specific tenants (e.g., teams, organizations, workspaces).
You MUST configure and enable tenancy in the config file BEFORE running the migrations. The migrations check the tenancy configuration to determine whether to add tenant columns to the database tables. If you enable tenancy after running migrations, you'll need to manually add the tenant columns to your database.
- Configure tenancy in
config/filament-lms.phpBEFORE running migrations:
'tenancy' => [
'enabled' => true,
'model' => \App\Models\Team::class,
'relationship_name' => 'team', // optional
'column' => 'team_id', // optional
],- Run migrations (which will now include tenant columns):
php artisan migrate- Implement required contracts on your User model:
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasTenants;
use Filament\Panel;
use Illuminate\Support\Collection;
class User extends Authenticatable implements FilamentUser, HasTenants
{
// Allow users to access the LMS panel
public function canAccessPanel(Panel $panel): bool
{
return true; // Or add custom logic
}
// Define the teams relationship
public function teams(): BelongsToMany
{
return $this->belongsToMany(Team::class);
}
// Return all teams the user can access
public function getTenants(Panel $panel): Collection
{
return $this->teams;
}
// Check if user can access a specific team
public function canAccessTenant(Model $tenant): bool
{
return $this->teams()->whereKey($tenant)->exists();
}
}- Implement HasName contract on your Tenant model:
use Filament\Models\Contracts\HasName;
class Team extends Model implements HasName
{
public function getFilamentName(): string
{
return $this->name;
}
}Once tenancy is enabled:
URL Structure Changes:
- LMS URLs are now scoped to tenants:
/lms/{tenant}/... - Example:
/lms/acme-corp/courses,/lms/acme-corp/certificates/... - The
{tenant}slug is automatically determined from your tenant model's route key
Data Scoping:
- All LMS queries are automatically scoped to the current tenant
- New courses, lessons, and materials are automatically associated with the current tenant
- Users can only access LMS content belonging to their current tenant
Permission Checking:
- Filament automatically verifies users have access to the tenant via
canAccessTenant() - Users can only see tenants returned by
getTenants() - Panel access is controlled by
canAccessPanel()
contains the LMS experience for the end user
- user can view courses available to them
- completion status
- shown when progressing through a single course
- left sidebar showing lessons with steps expanding from them
- icons in sidebar indicating the type of material for each step
- middleware to resume current step
- middleware to prevent skipping steps
- profile
- anything else?
(should these be resource groups in existing panel or its own panel?)
- Top level of learning material data structure
- courses do not have an order. they are independant
- courses can be public or invite only
- Intermediary level data structure
- Has Order (e.g. lesson 1 must be completed before starting lesson 2)
- in the future we may want to add support for lessons containing lessons to allow clients more customizability (lesson 1 contains lesson 1.1)
- name optional
- Represents a single view in the LMS
- has order
- has material
- name optional
- Video (do we use vimeo or something else?)
- Survey (form for student to fill out)
- Quiz (unlike a survey, a quiz has correct answers and a score)
- Text (Wysiwyg?)
- Image
This is the contents of the published config file:
<?php
use Filament\Navigation\NavigationItem;
return [
'theme' => 'default',
'font' => 'Poppins',
'home_url' => '/lms',
'brand_name' => 'LMS',
'brand_logo' => '',
'brand_logo_height' => null,
// Certificate customization
'certificate_logo' => '', // Falls back to brand_logo if not set
'certificate_show_signatures' => true, // Show signature lines on certificates
'certificate_show_id' => true, // Show unique certificate ID
'vite_theme' => '',
'colors' => [],
'awards' => [
'Default' => 'default',
],
'top_navigation' => false,
'show_exit_lms_link' => true,
];Set it to true to enable top navigation on the LMS dashboard (courses list page).
Note: This configuration only affects the dashboard. Course pages (when viewing individual steps) always use sidebar navigation, regardless of this setting.
Use to display or not the Exit LMS link on top bar.
The LMS package generates PDF certificates when users complete courses. You can customize the appearance and content of certificates using the following configuration options:
Specify a custom logo to display on certificates. If not set, it falls back to the brand_logo setting.
Recommended logo dimensions: Maximum height of 90px for optimal certificate layout.
'certificate_logo' => 'images/certificate-logo.png',Note: The path should be relative to your public directory or use a full URL.
Control whether signature lines are displayed on certificates. Default is true.
'certificate_show_signatures' => true, // Show signature lines
'certificate_show_signatures' => false, // Hide signature linesWhen enabled, the certificate will display signature lines for:
- An authorized signature (e.g., course instructor or administrator)
- The learner's signature
Control whether a unique certificate ID is displayed on certificates. Default is true.
'certificate_show_id' => true, // Show unique certificate ID
'certificate_show_id' => false, // Hide certificate IDThe certificate ID helps with verification and tracking of issued certificates.
Here's a complete example of certificate customization in your config/filament-lms.php:
return [
// General branding
'brand_name' => 'Acme Learning Academy',
'brand_logo' => 'images/brand-logo.png',
// Certificate-specific settings
'certificate_logo' => 'images/certificate-seal.png', // Use a different logo for certificates
'certificate_show_signatures' => true, // Include signature lines
'certificate_show_id' => true, // Include unique certificate ID
// Other settings...
];To register new navigation items in the LMS panel, use the boot() method of your AppPanelProvider.php file:
use Tapp\FilamentLms\LmsNavigation;
use Filament\Navigation\NavigationItem;
public function boot(): void
{
LmsNavigation::addNavigation('lms',
NavigationItem::make('Home')
->icon('heroicon-o-home')
->url(fn (): string => '/'),
);
}The LMS package uses Laravel Gates for authorization. You'll need to define the following Gates in your application's AuthServiceProvider:
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
public function boot()
{
Gate::define('viewLmsReporting', function ($user) {
// Customize this based on your application's needs
return $user->hasRole('admin') || $user->hasPermission('view-lms-reporting');
});
}
}viewLmsReporting: Controls access to the LMS reporting page. Users must pass this Gate check to view the reporting interface.
To restrict users to only see courses they are assigned to, set the following in your config/filament-lms.php:
'restrict_course_visibility' => true,When enabled, users will only see courses assigned to them via the lms_course_user pivot table.
This package provides a reusable CoursesRelationManager and AssignCoursesBulkAction for managing user-course assignments. To enable course assignment in your User resource:
- Import the Relation Manager and Bulk Action:
use Tapp\FilamentLms\RelationManagers\CoursesRelationManager;
use Tapp\FilamentLms\Actions\AssignCoursesBulkAction;- Register the CoursesRelationManager:
// In your UserResource.php
public static function getRelations(): array
{
return [
CoursesRelationManager::class,
// ... other relation managers ...
];
}- Add the AssignCoursesBulkAction to your bulk actions:
// In your UserResource.php
public static function table(Table $table): Table
{
return $table
// ...
->bulkActions([
AssignCoursesBulkAction::make(),
// ... other bulk actions ...
]);
}If you need custom logic to determine whether a course is visible to a user, you can override the isCourseVisibleForUser method provided by the FilamentLmsUser trait in your User model. This method is used to filter which courses are shown to the user when course visibility restrictions are enabled.
If you want to call the trait's original method within your override, you can alias it when importing the trait:
use Tapp\FilamentLms\Traits\FilamentLmsUser;
class User extends Authenticatable
{
use FilamentLmsUser {
FilamentLmsUser::isCourseVisibleForUser as filamentLmsIsCourseVisibleForUser;
}
// ...
public function isCourseVisibleForUser($course): bool
{
if ($this->hasAnyRole('admin', 'super_admin')) {
return true;
}
// Call the trait's original method
return $this->filamentLmsIsCourseVisibleForUser($course);
}
}This allows you to implement any business rules you need for course visibility, while still leveraging the default logic from the trait if desired.
Course evaluations let you require learners to complete a feedback form after finishing a training course and before the primary course is marked complete or a certificate is issued.
This uses a linked evaluation course model: the training course points to a separate private evaluation course via evaluation_course_id. Learners are auto-assigned to the evaluation course when they finish the training content.
- Filament Form Builder installed with its migrations published
- Package migrations published and run (including
add_evaluation_course_id_to_lms_courses_table) - A feedback form created in Form Builder (via the admin UI or an application-specific migration)
In config/filament-lms.php:
'evaluations' => [
'enabled' => true,
],Create a feedback form in Form Builder, then attach it to the evaluation course's form step.
Create two courses:
| Course | Settings |
|---|---|
| Training course (e.g. "Workplace Safety") | Public or private as needed. Set Evaluation course to the evaluation course below. |
| Evaluation course (e.g. "Workplace Safety Evaluation") | Must be private. Leave Evaluation course empty. Add one lesson with a Form step using your feedback form. |
Important:
- Set the evaluation link on the training course, not on the evaluation course. Linking the evaluation course back to the training course will hide the training course from the dashboard.
- Only private courses can be selected as evaluation targets.
- An evaluation course cannot itself have an evaluation course linked to it.
- The same evaluation course can be linked from multiple training courses. Each training course still requires its own evaluation submission before that course is marked complete or its certificate is issued.
- Learner completes all steps on the training course.
- If an evaluation is required, they see a Complete Evaluation prompt instead of the certificate.
- They are auto-enrolled on the private evaluation course (no manual assignment needed for public training courses).
- After submitting the evaluation form, they are redirected to the training course certificate page.
- After completion, learners can review their submission via View Evaluation on the certificate page.
Evaluation form steps do not show a Next button — Submit completes the step and advances automatically.
- Public training courses: learners do not need to be assigned to the training course. They are auto-assigned to the evaluation course when training is complete.
- Private training courses: learners must be assigned to the training course as usual. The evaluation course is still auto-assigned when they finish.
The LMS package provides a flexible way to control which steps users can access through the canAccessStep method. This method is available on any model that uses the FilamentLmsUser trait and can be overridden to implement custom access control logic.
By default, the canAccessStep method checks if the step is available based on the completion of previous steps in the course. This ensures users must complete steps in the proper sequence.
To implement custom step access logic, override the canAccessStep method in your User model:
use Tapp\FilamentLms\Traits\FilamentLmsUser;
class User extends Authenticatable
{
use FilamentLmsUser;
// ...
public function canAccessStep(Step $step): bool
{
// Allow admins to access all steps
if ($this->hasRole('admin')) {
return true;
}
// Fall back to default behavior (sequential step completion)
return parent::canAccessStep($step);
}
}The canAccessStep method is automatically used in the following places:
- Step Page Access: When users try to access a step directly via URL
- Navigation Links: Determines which step links are clickable in the course navigation
- Current Step Detection: Used when determining which step to redirect users to
The canAccessStep method works alongside the existing getAvailableAttribute() method in the Step model. While getAvailableAttribute() handles the basic sequential completion logic, canAccessStep provides an additional layer of access control that can be customized per user.
The LMS package also provides a way to control which users can edit steps through the canEditStep method. This method is separate from canAccessStep and specifically controls edit permissions.
By default, the canEditStep method returns false, meaning no users have edit permissions. This ensures that step editing is disabled by default and must be explicitly enabled.
To implement custom step edit logic, override the canEditStep method in your User model:
use Tapp\FilamentLms\Traits\FilamentLmsUser;
class User extends Authenticatable
{
use FilamentLmsUser;
// ...
public function canEditStep(Step $step): bool
{
// Allow admins to edit all steps
if ($this->hasRole('admin') || $this->hasRole('super_admin')) {
return true;
}
// Allow course creators to edit their own courses
if ($this->hasRole('instructor') && $step->lesson->course->created_by === $this->id) {
return true;
}
// No edit permissions for other users
return false;
}
}The canEditStep method is used in the following places:
- Edit Button Visibility: Controls whether the "Edit" button appears on step pages
- Admin Interface: Can be used to control access to step editing in the Filament admin panel
- API Endpoints: Can be used to secure step editing API endpoints
Note that canEditStep is separate from canAccessStep:
canAccessStep: Controls whether a user can view/access a stepcanEditStep: Controls whether a user can edit/modify a step
This separation allows for fine-grained control over user permissions, where users might be able to view steps but not edit them.