LifterLMS WooCommerce Hooks
This Add-On is included in the Earth Bundle, Universe Bundle, and Infinity Bundle plans.
All bundle plans include unlimited priority support, automatic updates, and additional benefits.
Hooks let you change how the LifterLMS WooCommerce integration behaves without editing the add-on. Actions run your code when something happens, such as an order enrolling a student. Filters let you change a value before the integration uses it, such as the text on the Members Only button. If hooks are new to you, start with the WordPress developer guide to hooks and the core function add_filter().
Your code can live in a child theme’s functions.php file, a site-specific plugin, or a code snippets plugin, which is the approach shown in the video below. Test on a staging site and keep a backup before adding code to a live site.
This reference covers every action and filter in LifterLMS WooCommerce 3.1.1. Each entry lists the version it arrived in, its parameters, and a working example. The integration also relies on hooks from LifterLMS itself; see LifterLMS Filters, the LifterLMS Action Reference, and the full LifterLMS Code Reference.
If you are looking to change something and cannot find a hook for it here, check the add-on code first and then submit a support ticket if you need help locating one.
In this Feature Friday, Chris Badgett and Emily Middleton tour the hooks on this page and build a custom order-notes snippet with the help of AI. The snippet replaces the default enrollment note on a WooCommerce order with links to edit the course and view the student’s progress report, and when access is removed it records how far the student got in the course. A version of that snippet appears below under llms_wc_add_{$type}_notes.
Knowing where each hook fires makes it much easier to pick the right one. This is the path a WooCommerce order takes through the integration.
- Cart and checkout validation. Every time WooCommerce checks the cart, the integration validates each product linked to a course or membership: members-only restriction, existing enrollment, course capacity, and the course enrollment window. Each check has its own filter (see Cart and Purchase Validation below).
- Order created. The integration saves the linked access plan IDs on each order line item. Helper functions such as
llms_get_llms_plans_in_wc_order()read them back later. - Order reaches the enrollment status. When the order moves to the Order Enrollment Status set in the integration settings (Completed by default; for WooCommerce Subscriptions, the Subscription Enrollment Status, Active by default), the integration loops through every plan on every line item.
- Fulfillment. For each plan it fires the
llms_wc_order_item_fulfillaction, then asks thellms_wc_do_default_enrollmentfilter whether to run its own enrollment. - Enrollment. It enrolls the customer with the trigger string
wc_order_{order_id}, records a WooCommerce order note (controlled byllms_wc_add_enrollment_notes), and if the plan has limited access it schedules thellms_wc_access_plan_expirationaction for the expiration date. - Purchase actions. When the order reaches Completed, the integration fires the core
lifterlms_product_purchasedandlifterlms_access_plan_purchasedactions so LifterLMS engagements with purchase triggers work for WooCommerce orders. - Unenrollment. When the order moves to one of the Order Unenrollment Statuses, the same loop runs:
llms_wc_do_default_enrollmentdecides whether to proceed,llms_wc_unenrollment_new_statussets the new enrollment status, the student is unenrolled, and a note is recorded (controlled byllms_wc_add_unenrollment_notes). - Trash and delete. Trashing an order unenrolls its students, restoring it re-enrolls them if the order status still matches, and permanently deleting the order items deletes the enrollment records.
llms_wc_do_default_enrollmentapplies in each case.
Actions notify your code that something happened. Hook them with add_action().
Two actions fire while the integration fulfills an order: one for each access plan when the order reaches its enrollment status, and one when a limited-access plan expires.
Fires once for each access plan linked to a WooCommerce order line item when the order reaches its enrollment status. It runs before the integration’s own enrollment, so you can fulfill the purchase your own way, for example by issuing seats or vouchers based on the line item quantity. Pair it with llms_wc_do_default_enrollment when your code replaces the default enrollment.
Since 3.1.0
Parameters
WC_Order$orderThe WooCommerce order. For subscriptions this is the parent order.WC_Order_Item$itemThe order line item.LLMS_Access_Plan$planThe linked access plan.int$quantityThe line item quantity.
Example Record the quantity purchased for each plan as an order note.
add_action( 'llms_wc_order_item_fulfill', function ( $order, $item, $plan, $quantity ) {
$order->add_order_note( sprintf(
'%1$d seat(s) purchased for the "%2$s" access plan.',
$quantity,
$plan->get( 'title' )
) );
}, 10, 4 );
A scheduled action, run through Action Scheduler, that fires when a limited-access plan bought through WooCommerce expires. The integration schedules it during enrollment for plans with a limited access period or an access end date, and its own callback runs at priority 10 to unenroll the student with the status expired and add an order note. Hook in at a later priority to react after access ends, or at an earlier priority to act before.
Since 2.0.0
Parameters
int$order_idWP Post ID of the WooCommerce order.int$product_idWP Post ID of the LifterLMS course or membership.
You can look up a pending expiration with LLMS_WC_Order_Actions::get_expiration( $order_id, $product_id ), which returns the timestamp or false.
Example Email the customer after their access expires.
add_action( 'llms_wc_access_plan_expiration', function ( $order_id, $product_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
wp_mail(
$order->get_billing_email(),
'Your access has ended',
sprintf( 'Your access to "%s" has expired. Visit the shop to renew.', get_the_title( $product_id ) )
);
}, 20, 2 );
Four actions fire while the add-on installs or updates itself. They are useful for clearing caches or logging, and rarely needed otherwise.
Runs after LifterLMS WooCommerce is updated to a new version, once the install routine has finished.
Since 1.3.6
Example Flush a custom cache after each add-on update.
add_action( 'llms_wc_updated', function () {
delete_transient( 'my_course_product_map' );
} );
Runs before LifterLMS WooCommerce database upgrades are run.
Since 2.0.0
Example
add_action( 'llms_wc_before_install', function () {
error_log( 'LifterLMS WooCommerce install starting.' );
} );
Runs after LifterLMS WooCommerce database upgrades are run.
Since 2.0.0
Example
add_action( 'llms_wc_after_install', function () {
error_log( 'LifterLMS WooCommerce install finished.' );
} );
Runs when LifterLMS WooCommerce background updates are queued, before they are saved and dispatched.
Since 2.0.0
Example
add_action( 'llms_wc_updates_queued', function () {
error_log( 'LifterLMS WooCommerce background updates queued.' );
} );
Filters let you change a value before the integration uses it. Hook them with add_filter() and always return a value.
These filters run while a WooCommerce order status change enrolls or unenrolls a student. See How Enrollment Flows Through the Integration above for the order in which they fire.
Decides whether the integration runs its own enrollment for an access plan when a WooCommerce order changes status. Return false to skip it, for example when your code or another add-on grants access through its own fulfillment path. The same filter runs for enrollment, unenrollment, and enrollment deletion, so return consistent results or access will be granted but never revoked.
Since 3.1.0
Parameters
bool$do_enrollmentWhether to run the default enrollment. Defaulttrue.LLMS_Access_Plan$planThe linked access plan.WC_Order_Item$itemThe order line item.WC_Order$orderThe WooCommerce order.
Example Skip the default enrollment for multi-seat purchases so your own fulfillment code can handle them.
add_filter( 'llms_wc_do_default_enrollment', function ( $do_enrollment, $plan, $item, $order ) {
if ( $item->get_quantity() > 1 ) {
return false;
}
return $do_enrollment;
}, 10, 4 );
Sets the enrollment status a student receives when a WooCommerce order status change unenrolls them. Valid values are expired and cancelled.
Since 2.0.0
Parameters
string$statusThe new enrollment status. Defaultexpired.int$order_idWP Post ID of the WooCommerce order.
Example Mark enrollments as cancelled, rather than expired, when the order was refunded.
add_filter( 'llms_wc_unenrollment_new_status', function ( $status, $order_id ) {
$order = wc_get_order( $order_id );
if ( $order && $order->has_status( 'refunded' ) ) {
return 'cancelled';
}
return $status;
}, 10, 2 );
Decides whether the integration records a note on the WooCommerce order when it enrolls or unenrolls a student. {$type} is either enrollment or unenrollment, so the two hook names are llms_wc_add_enrollment_notes and llms_wc_add_unenrollment_notes.
Since 1.3.0
Parameters
bool$add_notesWhether to record the note. Defaulttrue.
Example Turn off the default enrollment notes.
add_filter( 'llms_wc_add_enrollment_notes', '__return_false' );
Example Replace both default notes with your own, as shown in the video. The enrollment note links to the course editor and the student’s progress report. The unenrollment note records how far the student got.
// Turn off the integration's default notes.
add_filter( 'llms_wc_add_enrollment_notes', '__return_false' );
add_filter( 'llms_wc_add_unenrollment_notes', '__return_false' );
// Find the WooCommerce order behind an enrollment trigger like "wc_order_123".
function my_llms_wc_order_from_trigger( $trigger ) {
if ( 0 !== strpos( (string) $trigger, 'wc_order_' ) ) {
return false;
}
return wc_get_order( absint( str_replace( 'wc_order_', '', $trigger ) ) );
}
// Enrollment note with admin links.
add_action( 'llms_user_enrolled_in_course', function ( $student_id, $course_id ) {
$student = llms_get_student( $student_id );
$order = $student ? my_llms_wc_order_from_trigger( $student->get_enrollment_trigger( $course_id ) ) : false;
if ( ! $order ) {
return;
}
$report = admin_url( 'admin.php?page=llms-reporting&tab=students&stab=courses&student_id=' . $student_id . '&course_id=' . $course_id );
$order->add_order_note( sprintf(
'Enrolled in "%1$s". Edit course: %2$s Progress report: %3$s',
get_the_title( $course_id ),
get_edit_post_link( $course_id, '' ),
$report
) );
}, 10, 2 );
// Unenrollment note with the student's progress.
add_action( 'llms_user_removed_from_course', function ( $student_id, $course_id, $trigger, $new_status ) {
$order = my_llms_wc_order_from_trigger( $trigger );
$student = llms_get_student( $student_id );
$course = llms_get_post( $course_id );
if ( ! $order || ! $student || ! $course ) {
return;
}
$lesson_ids = $course->get_lessons( 'ids' );
$completed = 0;
foreach ( $lesson_ids as $lesson_id ) {
if ( $student->is_complete( $lesson_id, 'lesson' ) ) {
$completed++;
}
}
$order->add_order_note( sprintf(
'Access to "%1$s" was removed. New status: %2$s. Student was %3$s%% complete with %4$d of %5$d lessons finished.',
$course->get( 'title' ),
$new_status,
$student->get_progress( $course_id, 'course' ),
$completed,
count( $lesson_ids )
) );
}, 10, 4 );
The integration validates every linked product each time WooCommerce checks the cart. Each check returns true to allow the purchase or a WP_Error whose message WooCommerce shows to the shopper. These filters let you allow a blocked purchase, block an allowed one, or change the message.
Decides whether the current user can buy a product restricted to one or more LifterLMS memberships. The integration passes true when the user is a member and a WP_Error when they are not.
The integration currently fires this check under two names. When the user is blocked it fires llms_user_can_buy_members_only_wc_product (without wc_). When the user is allowed it fires llms_wc_user_can_buy_members_only_wc_product. Hook both names to cover both cases.
Since 2.3.0
Parameters
bool|WP_Error$can_buytrueif the user can buy the product, otherwise aWP_Error.int$user_idWP User ID of the current user.WC_Product$wc_productThe WooCommerce product or variation.int$membership_idWP Post ID of the first restricting membership.int[]$membership_idsWP Post IDs of all restricting memberships. Added in 3.0.0.
This filter affects cart validation only. The Members Only button still replaces the add to cart button for non-members on the shop and product pages.
Example Let site administrators buy members-only products for testing.
$my_allow_admins = function ( $can_buy, $user_id, $wc_product, $membership_id, $membership_ids ) {
if ( user_can( $user_id, 'manage_options' ) ) {
return true;
}
return $can_buy;
};
add_filter( 'llms_user_can_buy_members_only_wc_product', $my_allow_admins, 10, 5 );
add_filter( 'llms_wc_user_can_buy_members_only_wc_product', $my_allow_admins, 10, 5 );
Decides whether a logged-in user who is already enrolled in a course or membership linked to a product can buy that product again. By default the purchase is blocked with a message asking them to remove the product from the cart.
Since 2.3.0
Parameters
bool|WP_Error$not_enrolledtrueif the purchase is allowed, otherwise aWP_Error.int$user_idWP User ID of the current user.WC_Product$wc_productThe WooCommerce product or variation.
Example Allow repeat purchases of every linked product.
add_filter( 'llms_wc_user_not_already_enrolled_into_related_llms_products', '__return_true' );
Example Allow repeat purchases of one product only.
add_filter( 'llms_wc_user_not_already_enrolled_into_related_llms_products', function ( $not_enrolled, $user_id, $wc_product ) {
if ( 123 === $wc_product->get_id() ) {
return true;
}
return $not_enrolled;
}, 10, 3 );
Decides whether a product linked to a course that has reached its enrollment capacity can be purchased. By default the purchase is blocked with the course’s capacity message.
Since 3.0.0
Parameters
bool|WP_Error$has_capacitytrueif the purchase is allowed, otherwise aWP_Error.WC_Product$wc_productThe WooCommerce product or variation.
Example Replace the sold-out message with your own.
add_filter( 'llms_wc_is_course_not_at_capacity', function ( $has_capacity, $wc_product ) {
if ( is_wp_error( $has_capacity ) ) {
return new WP_Error(
'llms-invalid-product-no-capacity',
sprintf( '"%s" is full. Join the waitlist to hear about the next cohort.', $wc_product->get_title() )
);
}
return $has_capacity;
}, 10, 2 );
Decides whether a product linked to a course can be purchased while the course’s enrollment period is closed or has not opened yet. By default the purchase is blocked with the course’s enrollment closed or enrollment opens message, matching the restriction on the LifterLMS checkout.
Since 3.0.1
Parameters
bool|WP_Error$is_opentrueif the purchase is allowed, otherwise aWP_Error.WC_Product$wc_productThe WooCommerce product or variation.
Example Allow pre-sales before the enrollment window opens.
add_filter( 'llms_wc_is_course_enrollment_window_open', '__return_true' );
When a product is restricted to a membership, the integration replaces the add to cart button with a Members Only button that links to the membership. These filters control that button.
Changes the full HTML of the Members Only button.
Since 2.0.0
Parameters
string$htmlHTML of the button.int$post_idWP Post ID of the WooCommerce product or variation.int$membership_idWP Post ID of the LifterLMS membership the button links to. Added in 2.1.2.
Example Send the button to your pricing page instead of the membership page.
add_filter( 'llms_wc_members_only_button_html', function ( $html, $post_id, $membership_id ) {
return sprintf(
'<a class="llms-wc-members-only-button button" href="%1$s">%2$s</a>',
esc_url( home_url( '/pricing/' ) ),
esc_html( LLMS_WC_Availability_Buttons::get_button_text( $post_id ) )
);
}, 10, 3 );
Changes the default text of the Members Only button. This text is used only when no button text has been saved on the product.
Since 2.0.0
Parameters
string$textDefault text of the button.int$post_idWP Post ID of the WooCommerce product or variation.
Example
add_filter( 'llms_wc_members_only_button_default_text', function ( $text, $post_id ) {
return 'Join to Unlock';
}, 10, 2 );
Changes the text of the Members Only button after the saved or default text has been determined.
Since 2.0.0
Parameters
string$textText of the button.int$post_idWP Post ID of the WooCommerce product or variation.
Example Name the membership in the button.
add_filter( 'llms_wc_members_only_button_text', function ( $text, $post_id ) {
$membership_ids = llms_wc_get_product_membership_ids( $post_id );
if ( empty( $membership_ids ) ) {
return $text;
}
return sprintf( 'Available to %s Members', get_the_title( $membership_ids[0] ) );
}, 10, 2 );
These filters control how the integration treats access plans that are, or are not, linked to a WooCommerce product.
Changes the result of llms_wc_plan_has_wc_product(), which tells the integration whether an access plan is linked to a WooCommerce product. Linked plans use the WooCommerce price and send the Buy button to the WooCommerce cart. A plan counts as linked when it is not free and has a product selected.
Since 2.0.0
Parameters
bool$has_productWhether the plan has a linked WooCommerce product.LLMS_Access_Plan$planThe access plan.
Example Treat every plan as unlinked, which restores LifterLMS pricing and checkout for all plans. Useful when testing.
add_filter( 'llms_wc_plan_has_wc_product', '__return_false' );
Decides whether paid access plans that have no WooCommerce product are hidden from the course and membership pricing tables. By default they are hidden on the front end and shown in the admin. Free plans are always shown.
Since 2.0.13
Parameters
bool$filterWhether to hide unlinked paid plans. Defaulttrueon the front end,falsein the admin.
Example Show unlinked paid plans on the front end so they use the LifterLMS checkout.
add_filter( 'llms_wc_filter_unlinked_plans', '__return_false' );
On single product pages and on order item tables, the integration lists the courses and memberships linked to a product. These filters control that list.
Sets which post types appear in the linked content list on a product.
Since 2.0.0
Parameters
array$post_typesIndexed array of post types. Defaultarray( 'course', 'llms_membership' ).
Example List linked courses only, never memberships.
add_filter( 'llms_wc_allowed_relationship_types', function ( $post_types ) {
return array( 'course' );
} );
Changes the HTML of the whole linked content list output by llms_wc_get_product_association_list().
Since 2.0.8
Parameters
string$htmlHTML of the list.array$idsWP Post IDs of the linked courses or memberships.string$post_typeThe post type of the items in the list.
Example Hide the list on single product pages but keep it on order tables.
add_filter( 'llms_wc_get_product_association_list_html', function ( $html, $ids, $post_type ) {
if ( function_exists( 'is_product' ) && is_product() ) {
return '';
}
return $html;
}, 10, 3 );
Changes the HTML of each link in the linked content list. The integration uses this filter itself to append the access expiration date on order tables.
Since 2.0.8
Parameters
string$htmlHTML of the link.int$idWP Post ID of the course or membership.
Example Show the lesson count next to each linked course.
add_filter( 'llms_wc_get_product_association_list_item_html', function ( $html, $id ) {
if ( 'course' === get_post_type( $id ) ) {
$course = llms_get_post( $id );
$html .= sprintf( ' <small>(%d lessons)</small>', $course->get_lessons_count() );
}
return $html;
}, 10, 2 );
These filters control the WooCommerce My Account page tabs and how customer registration and profile data flow into LifterLMS.
Changes the LifterLMS student dashboard tabs that can be added to the WooCommerce My Account page. The array is keyed by the dashboard tab ID (view-courses, my-grades, view-memberships, view-achievements, view-certificates, notifications, redeem-voucher) and each item holds an endpoint slug, a title, and a content callback. Endpoints added here appear in the integration settings under Account Endpoints and must be enabled there. Changing a slug requires flushing permalinks.
Since 1.3.6
Parameters
array$endpointsArray of endpoint data.
Example Rename the My Courses tab and remove the voucher tab.
add_filter( 'llms_wc_account_endpoints', function ( $endpoints ) {
if ( isset( $endpoints['view-courses'] ) ) {
$endpoints['view-courses']['title'] = 'My Training';
}
unset( $endpoints['redeem-voucher'] );
return $endpoints;
} );
Example Add a custom tab with your own content.
add_filter( 'llms_wc_account_endpoints', function ( $endpoints ) {
$endpoints['my-resources'] = array(
'endpoint' => 'my-resources',
'title' => 'Resources',
'content' => function () {
echo '<p>Download your course resources here.</p>';
},
);
return $endpoints;
} );
Decides whether LifterLMS registration actions run when a customer registers through WooCommerce. When allowed, the integration fires the core lifterlms_user_registered action with 'wc_integration' as the third argument, which lets LifterLMS engagements and notifications treat the WooCommerce signup like a LifterLMS signup.
Since 2.0.0
Parameters
bool$triggerWhether to trigger the LifterLMS actions. Defaulttrue.int$customer_idWP User ID of the new customer.array$dataAssociative array of customer information.
Example Stop WooCommerce registrations from triggering LifterLMS registration engagements.
add_filter( 'llms_wc_trigger_registration_actions', '__return_false' );
Maps WooCommerce customer fields to LifterLMS student fields. When a customer saves their WooCommerce account details, each WooCommerce field in the map is copied to the matching LifterLMS field. The default map is:
Since 2.0.0
Parameters
array$mapAssociative array where the key is the WooCommerce user meta key and the value is the LifterLMS student field.
Example
array(
'billing_country' => 'billing_country',
'billing_address_1' => 'billing_address_1',
'billing_address_2' => 'billing_address_2',
'billing_city' => 'billing_city',
'billing_state' => 'billing_state',
'billing_postcode' => 'billing_zip',
'billing_phone' => 'phone',
)
Example Stop syncing the phone number.
add_filter( 'llms_wc_customer_keys_map', function ( $map ) {
unset( $map['billing_phone'] );
return $map;
} );
The integration also fires hooks that belong to LifterLMS itself, so code written for the LifterLMS checkout keeps working with WooCommerce orders. Full documentation for each lives in the LifterLMS Code Reference.
lifterlms_product_purchased( $user_id, $product_id )andlifterlms_access_plan_purchased( $user_id, $plan_id )fire when the WooCommerce order reaches Completed, once per linked access plan. LifterLMS engagements with purchase triggers rely on them.lifterlms_user_registered( $user_id, $data, 'wc_integration' )fires when a customer registers through WooCommerce, unlessllms_wc_trigger_registration_actionsreturnsfalse. The third argument identifies the source.llms_user_enrolled_in_course,llms_user_added_to_membership_level,llms_user_removed_from_course, andllms_user_removed_from_membership_levelfire as they do for any enrollment. The enrollment trigger iswc_order_{order_id}, which you can read withLLMS_Student::get_enrollment_trigger( $product_id )to find the order behind an enrollment.
Example Find the WooCommerce order that enrolled a student in a course.
$student = llms_get_student( $student_id );
$trigger = $student->get_enrollment_trigger( $course_id );
if ( 0 === strpos( (string) $trigger, 'wc_order_' ) ) {
$order = wc_get_order( absint( str_replace( 'wc_order_', '', $trigger ) ) );
}
These functions are available whenever the integration is active. They are useful inside the hook callbacks above.
| Function | Returns |
|---|---|
llms_get_llms_plans_by_wc_product_id( $product_id ) |
Access plan IDs linked to a product or variation. |
llms_get_llms_products_by_wc_product_id( $product_id ) |
Course and membership IDs linked to a product or variation. |
llms_get_llms_plans_in_wc_order( $order ) |
Access plan IDs across every item in a WooCommerce order. |
llms_get_llms_products_in_wc_order( $order ) |
Course and membership IDs across every item in a WooCommerce order. |
llms_wc_get_order_item_products( $item ) |
Course and membership IDs linked to one order line item. |
llms_wc_plan_has_wc_product( $plan ) |
Whether an access plan is linked to a WooCommerce product. |
llms_wc_is_product_variable( $product ) |
Whether a product is a variable product or variable subscription. |
llms_wc_get_product_membership_ids( $product_id ) |
Membership IDs that restrict a product, or an empty array. |
llms_wc_is_members_only_wc_product_restricted_for_user( $product ) |
Whether the current user is blocked from a members-only product. |
llms_wc_get_product_association_list( $ids, $post_type ) |
The HTML list of linked courses or memberships. |
LLMS_WC_Order_Actions::get_expiration( $order_id, $product_id ) |
Timestamp of a scheduled access expiration, or false. |
This Add-On is included in the Earth Bundle, Universe Bundle, and Infinity Bundle plans.
All bundle plans include unlimited priority support, automatic updates, and additional benefits.


