Transform Learning with a Powerful Enterprise LMS

Learn more
WordPress LMS

Snippet: Run Code When a User Joins or Leaves a LearnDash Group

Use ld_added_group_access and ld_removed_group_access to run code when users join or leave a LearnDash group, with a queued sync snippet and fixes for group and user deletion.

Learn more about this topic with AI

Gemini opens with the prompt copied - paste it into the chat to start.

Illustration of LearnDash group join and leave hooks triggering custom code

The LearnDash user added to group hook is ld_added_group_access. It fires with $user_id and $group_id each time a user becomes a member of a group, and its partner ld_removed_group_access fires with the same two arguments when a member is removed. Both fire from ld_update_group_access(), which every standard path uses: the group edit screen, the user profile, group purchases and the REST API. Hook them to sync a CRM, tag the user or start an onboarding sequence.

Below is a working pattern for both hooks, plus the two cases where membership changes without either hook firing. Checked against the LearnDash 4.18.0.1 source (October 2026); both hooks date from LearnDash 2.1.0. LearnDash 5.2.3 is the current release, and we have not reviewed the 5.x source, so spot-check includes/ld-groups.php on your version.

When the hooks fire

ld_update_group_access( $user_id, $group_id, $remove ) checks the user meta key learndash_group_users_{group_id} first. It only does work, and only fires a hook, when the state actually changes:

CallWritesFires
Add, user not yet a memberlearndash_group_users_{id}, learndash_group_{id}_enrolled_at, group_{id}_access_fromld_added_group_access
Remove, user is a memberDeletes the membership and access-from meta, and the user's group progress activityld_removed_group_access
Add an existing member, or remove a non-memberNothingNothing

So re-saving a group does not re-fire the hook for people already in it. The bulk setters learndash_set_groups_users() (used when a group is saved) and learndash_set_users_group_ids() (used from the user profile) work out the difference and call ld_update_group_access() once per added or removed user. The REST v1 and v2 group users routes, the Product model's enroll() and unenroll(), and the registration and payment flows call the same function, as does the personal data eraser.

Group Leaders are separate. Adding or removing a leader fires ld_added_leader_group_access and ld_removed_leader_group_access from ld_update_leader_group_access(), not the member hooks. Our article on the LearnDash Group Leader role explains how leaders differ from members.

The snippet

This records the join time and queues a sync job on join and leave. The job runs a few seconds later through WP-Cron, so the admin save, REST call or checkout that triggered the change is not slowed by a remote request. Not run on a live site; test on staging first.

<?php
/**
 * Plugin Name: AIOL LearnDash group membership sync
 * Description: Runs code when a user joins or leaves a LearnDash group.
 */

defined( 'ABSPATH' ) || exit;

add_action( 'ld_added_group_access', 'aiol_ld_group_joined', 10, 2 );
add_action( 'ld_removed_group_access', 'aiol_ld_group_left', 10, 2 );
add_action( 'aiol_ld_group_sync', 'aiol_ld_group_sync_run', 10, 3 );

function aiol_ld_group_joined( $user_id, $group_id ) {
	$user_id  = absint( $user_id );
	$group_id = absint( $group_id );
	update_user_meta( $user_id, 'aiol_group_joined_' . $group_id, time() );
	aiol_ld_group_queue( $user_id, $group_id, 'joined' );
}

function aiol_ld_group_left( $user_id, $group_id ) {
	aiol_ld_group_queue( absint( $user_id ), absint( $group_id ), 'left' );
}

function aiol_ld_group_queue( $user_id, $group_id, $event ) {
	$args = array( $user_id, $group_id, $event );
	if ( ! wp_next_scheduled( 'aiol_ld_group_sync', $args ) ) {
		wp_schedule_single_event( time() + 10, 'aiol_ld_group_sync', $args );
	}
}

function aiol_ld_group_sync_run( $user_id, $group_id, $event ) {
	$user = get_userdata( $user_id );
	if ( ! $user || ! defined( 'AIOL_CRM_WEBHOOK_URL' ) ) {
		return;
	}
	// Re-check the current state: the user may have been removed again since.
	$is_member = (bool) get_user_meta( $user_id, 'learndash_group_users_' . $group_id, true );
	wp_remote_post(
		AIOL_CRM_WEBHOOK_URL,
		array(
			'timeout' => 10,
			'headers' => array( 'Content-Type' => 'application/json' ),
			'body'    => wp_json_encode(
				array(
					'event'     => sanitize_key( $event ),
					'email'     => $user->user_email,
					'group_id'  => $group_id,
					'group'     => get_the_title( $group_id ),
					'is_member' => $is_member,
				)
			),
		)
	);
}

Put it in a small plugin or wp-content/mu-plugins, and define AIOL_CRM_WEBHOOK_URL in wp-config.php. Notes on the choices:

  • Both hooks pass two arguments, so register them with 10, 2, or the group ID never reaches your callback.
  • The job re-reads membership rather than trusting the event, so a quick add-then-remove sends the current state.
  • No capability or nonce check is needed here, because the hook runs inside a request LearnDash has already authorised. Do not call it from a front-end form of your own.
  • WP-Cron needs traffic or a real cron. On low-traffic sites, run wp-cron.php from a system cron so jobs are not delayed.

Test it on staging

You can trigger both hooks without clicking through the admin by calling the core function from WP-CLI, with a test user ID and group ID:

wp eval 'var_dump( ld_update_group_access( 12, 345 ) );'
wp eval 'var_dump( ld_update_group_access( 12, 345, true ) );'
wp cron event list

The first call returns true and fires the join hook only if user 12 was not already a member; a second identical call returns false and fires nothing. The cron list should show an aiol_ld_group_sync event for each change. Then repeat the change from the group's Users tab to confirm the admin path behaves the same.

Gaps: when membership changes without a hook

Two paths remove membership without calling ld_update_group_access():

  1. Deleting a group. learndash_delete_group() runs on delete_post and deletes every learndash_group_users_{id} and learndash_group_leaders_{id} row directly, so no removal hook fires. Moving a group to the trash does not remove members.
  2. Deleting a user. WordPress deletes the user's meta with the user, so their memberships disappear with no LearnDash hook.

If your external system must stay in step, catch both before the data goes. Not run on a live site; test on staging first:

<?php
add_action( 'before_delete_post', 'aiol_ld_group_before_delete', 10, 2 );
function aiol_ld_group_before_delete( $post_id, $post ) {
	if ( ! $post || learndash_get_post_type_slug( 'group' ) !== $post->post_type ) {
		return;
	}
	foreach ( learndash_get_groups_user_ids( $post_id ) as $user_id ) {
		aiol_ld_group_queue( $user_id, absint( $post_id ), 'left' );
	}
}

add_action( 'delete_user', 'aiol_ld_user_before_delete' );
function aiol_ld_user_before_delete( $user_id ) {
	foreach ( learndash_get_users_group_ids( $user_id, true ) as $group_id ) {
		aiol_ld_group_queue( absint( $user_id ), absint( $group_id ), 'left' );
	}
}

The queued job for a deleted user finds no user and returns, so for user deletion, send the email address in the event itself if your CRM needs it. With group hierarchy enabled, learndash_get_users_group_ids() also returns child groups the user can reach through a parent, which may be more than the direct memberships you want to report.

Things these hooks do not tell you

  • Course access through a group. Joining a group gives access to its courses without calling the course enrolment function, so learndash_update_course_access does not fire for each course. If you need per-course events, loop over learndash_group_enrolled_courses( $group_id ) in your join handler.
  • Courses added to a group. That is ld_added_course_group_access and ld_removed_course_group_access, which pass the course ID and group ID.
  • Group completion. learndash_group_completed fires when a member completes every course in the group.
  • Bulk removals. learndash_remove_group_users fires once with the group ID and the array of removed user IDs after a group save. The per-user removal hook has already fired for each of them.

For the functions that change membership from code, see adding a user to a LearnDash group programmatically and the LearnDash group functions reference. For the admin side, see how to create a LearnDash group.

How we can help

We build LearnDash integrations that keep group membership in step with CRMs, HR systems and billing, including back-fills for existing members and the edge cases above. See our LMS plugin development service.

Frequently asked questions

What hook fires when a user is added to a LearnDash group?

ld_added_group_access, with the user ID and group ID as arguments. It fires from ld_update_group_access() only when the user was not already a member. The matching removal hook is ld_removed_group_access.

Does ld_added_group_access fire for Group Leaders?

No. Leaders are stored separately, and adding one fires ld_added_leader_group_access; removing one fires ld_removed_leader_group_access. A Group Leader who is also listed as a member triggers the member hooks for that membership.

Does the hook fire when a group is deleted?

No. learndash_delete_group() deletes the membership meta rows directly on delete_post, so no removal hook fires. Use before_delete_post to read the members with learndash_get_groups_user_ids() before they are removed. Trashing a group does not remove members.

Does joining a group fire the course enrolment hook?

No. Group members get course access through the group, so learndash_update_course_access does not fire for each course. Loop over learndash_group_enrolled_courses() in your join handler if you need per-course events.

Free 30-minute consultation

Not sure where to start? Talk to an LMS engineer.

Tell us what platform you run and what you need. You’ll get honest, practical advice - even if the answer is that you don’t need us.

Sources & references

  1. Group Users and Group Leaders - LearnDash documentation
  2. wp_schedule_single_event() - WordPress Developer Resources
  3. before_delete_post hook - WordPress Developer Resources
  4. LearnDash LMS changelog - LearnDash
Add as a preferred source on Google

Add All-in-One LMS as a preferred source to see more of our articles in Google’s Top Stories.

Learn more about this topic with AI

Gemini opens with the prompt copied - paste it into the chat to start.

Keep reading

Before you go - get a free LMS audit

Tell us which LMS you use and what is not working. An engineer replies within one business day.

Choose which optional cookies we may use. You can change this at any time from "Cookie preferences" in the footer. Cookie policy · Privacy policy