How to Create a Course in LifterLMS: From Blank Page to Published
Build one LifterLMS course from start to finish: the course post, Course Options, the outline, lesson content, a quiz, an access plan, publishing…
Read articleSooner or later a LifterLMS site needs something the settings do not offer: a different course layout, an extra field at checkout, a renamed tab on the Student Dashboard. The plugin is built to be changed. What matters is where you put the change.
Edit a file inside wp-content/plugins/lifterlms/ and the next update overwrites your work. Put the same change in the right place and it keeps working. This tutorial shows where that place is for each kind of customization.
You need to be comfortable with a little PHP and CSS, and you should try everything on a staging copy first.
A lot of "customization" is already a setting, and a setting survives updates.
Both are upgrade-safe. The difference is what happens when you change themes.
| Kind of change | Where it belongs |
|---|---|
| Template overrides | Child theme, in a lifterlms/ folder |
| CSS | Child theme stylesheet |
| Layout tweaks tied to this theme | Child theme functions.php |
| Enrollment rules, automations, custom fields, integrations | A small custom plugin |
The rule of thumb: if it should survive a redesign, it goes in a plugin. WordPress documents how to create a child theme, and a custom plugin can start as a single PHP file with a header comment. When the snippets start to pile up, read when to build a custom LifterLMS plugin.
LifterLMS builds its front-end output from PHP templates in wp-content/plugins/lifterlms/templates/. To change one:
templates/course/lesson-preview.php.lifterlms folder in your child theme.templates/. The result is wp-content/themes/your-child-theme/lifterlms/course/lesson-preview.php.LifterLMS looks in the child theme, then the parent theme, then falls back to its own file. The LifterLMS template guide walks through the same steps.
The cost is that your copy is frozen. When LifterLMS later fixes a bug or improves accessibility in that template, your override does not get the fix. So:
@version line at the top of the copied file. LifterLMS > Status > System Report lists your overrides with their version beside the current core version.lifterlms folder for the first time. LifterLMS caches the list of override folders for up to a day.Hooks add or change behavior without copying a template. Put these in your custom plugin or child theme functions.php. The acme_ prefix and the acme text domain are placeholders for your own.
add_action( 'lifterlms_single_course_after_summary', 'acme_course_support_note', 95 );
function acme_course_support_note() {
if ( ! llms_is_user_enrolled( get_current_user_id(), get_the_ID() ) ) {
return;
}
echo '<p>' . esc_html__( 'Stuck on a lesson? Reply to your welcome email and we will help.', 'acme' ) . '</p>';
}
Enrolled students see the note; everyone else sees nothing. On a course built in the classic editor, priority 95 places it after the syllabus (90) and before reviews (100).
One caveat. On courses and lessons edited in the block editor, LifterLMS removes its own default callbacks for the syllabus, pricing table, instructors, progress and lesson navigation, because those are blocks in the content. Your note still prints, after the blocks, but a remove_action() aimed at those defaults does nothing. Delete or move the block instead.
add_filter( 'llms_get_student_dashboard_tabs', 'acme_rename_courses_tab' );
function acme_rename_courses_tab( $tabs ) {
if ( isset( $tabs['view-courses'] ) ) {
$tabs['view-courses']['title'] = __( 'My Programs', 'acme' );
}
return $tabs;
}
add_action( 'lifterlms_course_completed', 'acme_enroll_in_next_course', 10, 2 );
function acme_enroll_in_next_course( $student_id, $course_id ) {
$next = array(
123 => 456, // Finished course ID => next course ID.
);
if ( isset( $next[ $course_id ] ) ) {
llms_enroll_student( $student_id, $next[ $course_id ], 'acme_course_sequence' );
}
}
The third argument is a free-text label stored as the enrollment trigger, which helps later when you need to know why someone has access. The LifterLMS code reference lists hooks and functions with their parameters.
LifterLMS front-end classes start with llms-, such as .llms-syllabus-wrapper, .llms-lesson-preview and .llms-button-action. Put your rules in the child theme stylesheet or the theme's Additional CSS panel, never in the plugin's own CSS files.
.llms-syllabus-wrapper .llms-lesson-preview .llms-lesson-title {
font-weight: 600;
}
LifterLMS buttons also carry the WordPress wp-element-button class, so on a block theme they pick up the button styles you set for the theme. Check that before writing button CSS by hand.
You have three options, in order of effort:
lifterlms_get_person_fields filter appends fields to the forms.add_filter( 'lifterlms_get_person_fields', 'acme_add_company_field' );
function acme_add_company_field( $fields ) {
$fields[] = array(
'id' => 'acme_company',
'name' => 'acme_company',
'type' => 'text',
'label' => __( 'Company', 'acme' ),
'required' => false,
);
return $fields;
}
The value is saved to user meta under the field's name. The filter also passes the form location (checkout, registration or account) as a second parameter. If a field should appear on only some forms, register the filter with two accepted arguments and check it. To act on a new signup, hook lifterlms_user_registered, which receives the user ID, the submitted data and the form location.
WP_DEBUG and WP_DEBUG_LOG enabled, then check debug.log for deprecation notices that point at your own code.For each change on your list, pick the lightest tool that works: a setting, then a block, then CSS, then a hook, and a template override only when nothing else will do. Keep every snippet in version control with a comment saying why it exists.
It is worth getting help when a change touches checkout or access rules, when overrides have piled up and updates have become stressful, or when you have inherited a site where someone edited the plugin directly. Our LifterLMS customization service handles that kind of work, ongoing LifterLMS support covers updates, and you can ask us a question any time. For the bigger picture, see our guide to LifterLMS development.
Build one LifterLMS course from start to finish: the course post, Course Options, the outline, lesson content, a quiz, an access plan, publishing…
Read article
Know what every control in the LifterLMS Course Builder does: section and lesson actions, each field in the lesson settings panel, the quiz editor…
Read article
Add video to a LifterLMS lesson with the Video Embed URL field, choose a host that fits your content, keep large files out of the media library, and…
Read articleSkip the trial and error. Get specialist LifterLMS help and ship faster.