LifterLMS Development: Everything You Need to Know

LifterLMS development is WordPress development with extra rules: content lives in post types, student activity in custom tables, changes go through hooks and the public API, and staging has to be kept away from live billing.

  • Add as a preferred source on Google
Illustration for “LifterLMS Development: Everything You Need to Know”

LifterLMS development is WordPress development with a few extra rules. The plugin has its own data model for courses, sales and student progress, its own extension points, and some behavior that only shows up once real students and real payments are involved.

This guide explains how that work should be done. If you own the business, it tells you what your developer should be doing and why. If you write the code, it is the map to have open before you start.

Hook, function and post type names below are written exactly as they appear in the LifterLMS source at the time of writing.

The data model a developer meets

LifterLMS keeps its content in WordPress custom post types and most student activity in its own database tables. Knowing which is which decides how you query, export and migrate.

What it isWhere it lives
Courses, sections, lessonsPost types course, section, lesson
Quizzes and questionsllms_quiz, llms_question
Membershipsllms_membership
Access plans (the price and terms of a course or membership)llms_access_plan
Orders and their paymentsllms_order, llms_transaction
Engagements (automated emails, achievements, certificates)llms_engagement plus llms_email, llms_achievement, llms_certificate
Enrollments and progressTable {prefix}lifterlms_user_postmeta
Quiz attemptsTable {prefix}lifterlms_quiz_attempts

Two details catch people out. A section is its own post, so a course outline is three post types deep. And enrollment is not a user role or a row in wp_usermeta. It is a status record (enrolled, expired or cancelled) in the LifterLMS table, keyed by the user and the course or membership.

How a sale becomes an enrollment

An access plan belongs to one course or membership. When a student checks out, LifterLMS creates an order, records each payment as a transaction, and enrolls the student. Recurring plans keep adding transactions to the same order. Our guide to memberships and access plans covers the business side of this.

Order statuses are post statuses with an llms- prefix, such as llms-pending, llms-active, llms-completed, llms-on-hold and llms-failed. They are not interchangeable. An order that never heard back from the payment gateway stays pending; it does not become failed. Reports or automations that treat the two as one will be wrong.

For student data, use the PHP functions rather than SQL. llms_enroll_student(), llms_unenroll_student() and llms_is_user_enrolled() wrap the LLMS_Student class, and enrolling through them fires the actions that engagements, webhooks and add-ons listen for. Writing rows directly skips all of that.

The extension points

Actions and filters

Hooks are the main way to change behavior, and the LifterLMS code reference lists each one with its parameters and source file. New hooks use the llms_ prefix. Older ones keep lifterlms_ for backward compatibility, so expect to see both.

add_action( 'llms_user_enrolled_in_course', 'acme_log_enrollment', 10, 2 );

function acme_log_enrollment( $user_id, $course_id ) {
	llms_log(
		sprintf( 'User %d enrolled in course %d', $user_id, $course_id ),
		'acme-enrollments'
	);
}

That writes to its own log file, which you can read under LifterLMS > Status > Logs. Swap the function body for a CRM call and you have the pattern behind many integrations.

Template overrides

Front-end markup comes from PHP files in the plugin's templates/ folder. Copy one into a lifterlms/ folder in your child theme, keeping the same sub-path, and LifterLMS loads your copy instead. The details are in how to customize LifterLMS without breaking updates.

REST API and webhooks

The REST API ships with LifterLMS core under /wp-json/llms/v1/. It covers courses, sections, lessons, memberships, access plans, students, enrollments and progress, and recent releases added quizzes, quiz attempts, orders and certificates. Requests authenticate with API keys created under LifterLMS > Settings > REST API, and key authentication only works over HTTPS.

Webhooks are managed on the same screen. Each one posts JSON to your URL when something happens, for example enrollment.created or student.created, and signs the delivery with an X-LLMS-Webhook-Signature header so the receiver can verify it. By default deliveries are queued in the background instead of being sent during the student's page load.

WP-CLI, integrations and gateways

The wp llms commands manage courses, students, enrollments and progress from the shell, which helps with scripted setup and migrations. For a settings screen of your own, extend LLMS_Abstract_Integration and register the class through the lifterlms_integrations filter. Payment gateways extend LLMS_Payment_Gateway and register through lifterlms_payment_gateways. If the goal is connecting a CRM or email tool, check the existing LifterLMS integrations first.

A sane path from local to production

  1. Local. Build on your own machine with the same plugin versions as production, and keep all custom code in version control.
  2. Staging. Test new code and plugin updates on a recent copy of production with realistic data.
  3. Production. Deploy code that has already passed on staging. Nothing is edited live.

The rule that keeps you out of trouble: code moves up, data moves down. Production holds orders, enrollments and progress that change by the minute, so a staging database never gets pushed over it. Custom code goes up through version control. Course content built on staging can travel with the Export bulk action on the Courses list and LifterLMS > Import.

LifterLMS helps with one staging risk. When a copy's URL no longer matches the address the original site stored, LifterLMS treats it as a clone and stops automatic recurring payments there. Until recently that only took effect once an administrator had opened the dashboard on the copy, so do not rely on detection alone: define LLMS_SITE_IS_CLONE as true in the staging wp-config.php. Recurring payments are the only feature this protects. Engagement emails and webhooks still fire, so put gateways in test mode, route staging mail to a catcher, and disable or re-point webhooks.

Testing what matters

LMS bugs are rarely visual. They are a student who paid and cannot get in, or one who did not pay and can. Test the paths that carry money and access:

  • Checkout for every plan type you sell: free, one-time, recurring and trial, with and without a coupon.
  • Enrollment and unenrollment, including what happens when a recurring payment fails or access expires.
  • Content restriction as a logged-out visitor, an enrolled student and a member: drip, prerequisites and membership-only content.
  • Lesson completion, quiz attempts, and the engagements they should trigger.

Automate what you can. LifterLMS core has PHPUnit and Playwright end-to-end test suites, and custom plugins deserve the same for their own logic. For manual checks on staging, the built-in Manual payment gateway creates orders without a card, so you can walk an order through its statuses by hand.

What upgrade-safe means

Upgrade-safe code survives LifterLMS and WordPress updates without anyone re-applying changes by hand. In practice:

  • No edits to plugin files. Custom code lives in a child theme or your own plugin.
  • Public APIs only. Use documented functions, hooks and REST endpoints instead of private methods and direct table writes. The project's contributor rules say public function signatures, hook names and REST response shapes must not change silently, and that replacements go through deprecation. That is what makes them safe to build on.
  • Few template overrides, reviewed at every update. Releases that change templates list them in the LifterLMS changelog, and LifterLMS > Status > System Report shows your overrides next to the current core version.
  • Deprecations handled early. Fix deprecation notices while they are still warnings.
  • Staging first. Apply updates there, run the tests above, then update production.

What to do next

If you are planning a build, write down the data you need to store and the events you need to react to, then map each one to a post type, table, hook or endpoint from this guide. Whatever does not map cleanly is where the real design work is, and it may be a sign that you need a custom LifterLMS plugin.

Small changes are within reach of a careful in-house developer. Bring in specialists when the work touches checkout, access rules, recurring billing or a large migration, where mistakes cost revenue or student trust. That is what our LifterLMS development service is for, LifterLMS plugin development covers standalone extensions, and you can tell us about your project whenever you are ready.

Add LifterLMS Expert to your preferred sources

Add lifterlmsexpert as a preferred source on Google, or open this article in your AI assistant to use it as a source.

  • Add as a preferred source on Google
VishavjeetChoubeyLifterLMS Expert
Free consultation

Prefer expert help over DIY?

Skip the trial and error. Get specialist LifterLMS help and ship faster.

Talk to a LifterLMS Expert Explore our services

Replies within one business day