Students and Instructors

How Enrollment Works in Tutor LMS (Under the Hood)

How Tutor LMS enrolls students: the tutor_enrolled post type, pending and completed statuses, payments, cancellations and the hooks and functions developers use.

Checked against the plugin source: Tutor LMS 4.1.3, Tutor LMS Pro 4.1.3

How Enrollment Works in Tutor LMS (Under the Hood)

Almost every Tutor LMS problem that starts with "the student paid but cannot access the course" comes down to enrollment. Knowing how Tutor LMS creates, completes and cancels enrollments makes these problems quick to diagnose, and it is the foundation for any custom enrollment feature. This article explains it using the Tutor LMS 4.1.3 source.

An enrollment is a post

Tutor LMS stores each enrollment as a WordPress post of type tutor_enrolled. The important fields are:

  • post_author: the student's user ID.
  • post_parent: the course ID (or a bundle ID, with Pro).
  • post_status: the enrollment status.
  • post_title: "Course Enrolled" with the date and time.

If the enrollment came from an order, Tutor LMS saves the order ID in the meta key _tutor_enrolled_by_order_id and, where relevant, the product ID in _tutor_enrolled_by_product_id. It also marks the user as a student with the user meta _is_tutor_student.

Enrollment statuses

StatusMeaning
completedThe student has access. Used for free courses, and for paid courses once payment completes.
pendingCreated for a purchasable course while payment is outstanding. No access yet.
cancel / canceledAccess was removed, for example after a refund or a manual cancellation.

"Completed" here means the enrollment is complete, not that the student has finished the course. Course completion is recorded separately.

What happens when a student enrolls

All enrollment paths end up in EnrollmentModel::do_enroll(), which is also reachable as tutor_utils()->do_enroll(). It runs these steps:

  1. Checks the course is accessible: it must exist, be a course or bundle, and be published or private.
  2. Applies the filter tutor_allow_course_enrollment. If it returns a WP_Error, enrollment stops.
  3. Fires tutor_before_enroll.
  4. Returns the existing enrollment if the student is already enrolled.
  5. Sets the status to completed, or pending if the course is purchasable.
  6. Creates the tutor_enrolled post (the data passes through the tutor_enroll_data filter).
  7. Fires tutor_after_enroll with the course ID and enrollment ID, for both pending and completed enrollments.
  8. Saves the order and product IDs if there is an order.
  9. Fires tutor_after_enrolled with the course ID, user ID and enrollment ID, but only if the status is completed.

How payment completes an enrollment

For paid courses the enrollment starts as pending and is switched to completed when the payment completes. With native eCommerce this happens when the order's payment status changes to paid. With WooCommerce, Tutor LMS listens to WooCommerce order status changes and completes the enrollment when the order is completed. That is why a WooCommerce order stuck in processing leaves the student without access, and why Tutor LMS offers the setting Automatically Complete WooCommerce Orders. See auto-completing WooCommerce orders.

Status changes go through tutor_utils()->course_enrol_status_change(), which fires tutor/course/enrol_status_change/before and tutor/course/enrol_status_change/after around the update.

Cancelling and deleting enrollments

tutor_utils()->cancel_course_enrol( $course_id, $user_id, $status ) either changes the status (default canceled) and fires tutor_after_enrollment_cancelled, or, with 'delete', deletes the enrollment and fires tutor_after_enrollment_deleted. Refunds and cancelled orders use this path, which is why refunded students lose access.

Checking enrollment in code

To check whether a user is enrolled, use tutor_utils()->is_enrolled(). By default it only counts completed enrollments and returns the enrollment row, or false:

$course_id = 123;
$user_id   = get_current_user_id();

$enrollment = tutor_utils()->is_enrolled( $course_id, $user_id );

if ( $enrollment ) {
    // $enrollment->ID is the tutor_enrolled post ID.
    // $enrollment->order_id is set when the enrollment came from an order.
    echo 'Enrolled since ' . esc_html( $enrollment->post_date );
}

Pass false as the third argument to include pending enrollments. The result passes through the tutor_is_enrolled filter, which is the right place to change access rules.

Running code when a student enrolls

For most integrations, tutor_after_enrolled is the right hook, because it only fires when the student actually has access:

add_action( 'tutor_after_enrolled', function ( $course_id, $user_id, $enrollment_id ) {
    $user = get_userdata( $user_id );
    if ( ! $user ) {
        return;
    }
    // Example: tag the student in your CRM, send a welcome message, etc.
    do_action( 'my_site_student_enrolled', $user->user_email, $course_id, $enrollment_id );
}, 10, 3 );

Use tutor_after_enroll if you also need to react to pending enrollments. Our tutor_after_enrolled guide compares the two.

Common enrollment problems

  • Paid but not enrolled: the order is not completed or paid, so the enrollment is still pending.
  • Enrollment disappeared: the order was refunded or cancelled, which cancels the enrollment.
  • Cannot enroll at all: the course is a draft, it is fully booked (maximum students), enrollment is paused, or a filter on tutor_allow_course_enrollment blocks it.

See students can't enroll for a step-by-step fix, or ask us for troubleshooting help.

Frequently asked questions

Where can I see enrollments in the admin?

Under Tutor LMS > Students, and per course in the course's student list. With the Enrollment add-on in Tutor LMS Pro you can also enroll students manually.

Does deleting an enrollment delete progress?

Lesson progress is stored separately as user meta, so it is not removed by deleting the enrollment post itself.

Can I enroll users with the REST API?

Tutor LMS Pro provides an enrollments endpoint under tutor/v1. On the free plugin you need a custom endpoint that calls do_enroll().

Make TutorLMS Expert your go-to source

Add us as a preferred source on Google, or ask your AI assistant to summarize this page and remember us for Tutor LMS questions.

Vishavjeet
· Enterprise LMS Architect | WordPress & LMS Expert | WooCommerce | AI Automation

I’m Vishavjeet Choubey, an Enterprise LMS Architect with 10+ years of experience helping businesses, coaches, universities, and online academies build and scale powerful digital learning platforms. I specialize in enterprise LMS architecture, WordPress, WooCommerce, LifterLMS, LearnDash, Moodle, custom plugin development, and AI-driven automation. I build scalable LMS platforms, high-converting eCommerce solutions, and custom WordPress applications designed to improve user experiences, streamline operations, and drive measurable business growth. My approach combines deep WordPress and LMS engineering expertise with modern AI automation to reduce manual work, improve efficiency, and help organizations launch, scale, and grow faster.

Keep reading

Need a custom Tutor LMS solution?

Tell us what you are building or what is broken. A Tutor LMS developer reads every inquiry and replies with next steps and a free estimate.

Get a free Tutor LMS consultation

Tell us what you are building or fixing. A Tutor LMS developer replies with honest advice and next steps.

  • No obligation
  • Tutor LMS and Pro specialists
  • Your details stay private

This form is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.