# Learndash Group Access

> Build or audit LearnDash group membership, group-course access, group leaders, and hierarchical group behavior. Use when code mentions ld_update_group_access, ld_update_course_group_access, learndash_get_users_group_ids, learndash_get_groups_user_ids, learndash_group_enrolled_courses, learndash_user_group_enrolled_to_course, learndash_is_user_in_group, learndash_group_users_*, learndash_group_enrolled_*, group_*_access_from, or group leader access.

- Skill: `lonsdale201/learndash-group-access` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add lonsdale201/learndash-group-access`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lonsdale201/learndash-group-access/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: Lonsdale201 (https://skillmd.com/u/lonsdale201)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/lonsdale201/learndash-group-access

---


# LearnDash group access

Use this when code needs to enroll users into groups, attach courses to groups,
manage group leaders, or answer "does this user get course access through a
group?"

LearnDash groups are not just taxonomy terms and not just post meta on a group.
In 5.1.9 the access model is three separate relationships:

- User -> group membership.
- Group -> course access.
- Group leader -> administrated group.

Course access through a group exists only when the user is in the group and the
group is attached to the course, then the group product start/end rules allow
access.

## When to use this skill

Trigger when ANY of these appears:

- `ld_update_group_access()`, `ld_update_course_group_access()`,
  `ld_update_leader_group_access()`, `learndash_is_user_in_group()`,
  `learndash_user_group_enrolled_to_course()`, or
  `learndash_get_users_group_ids()`.
- Usermeta keys `learndash_group_users_{$group_id}`,
  `group_{$group_id}_access_from`,
  `learndash_group_{$group_id}_enrolled_at`, or
  `learndash_group_leaders_{$group_id}`.
- Course postmeta keys `learndash_group_enrolled_{$group_id}` or
  `learndash_group_{$group_id}_enrolled_at`.
- Custom group dashboards, group leader portals, imports, CRMs, Woo bridges,
  memberships, cohort sync, or REST endpoints.

## Relationship map

### User -> group

The direct user-group relation is stored on the user:

- `learndash_group_users_{$group_id}` stores the group ID.
- `group_{$group_id}_access_from` stores the access start timestamp.
- `learndash_group_{$group_id}_enrolled_at` is historical enrollment time.

Use:

```php
ld_update_group_access( $user_id, $group_id );        // grant
ld_update_group_access( $user_id, $group_id, true );  // revoke
```

Or use the model wrapper:

```php
use LearnDash\Core\Models\Product;

$group_product = Product::find( $group_id );

if ( $group_product ) {
    $group_product->enroll( $user_id );
}
```

`ld_update_group_access()` creates access activity for courses currently
attached to the group, fires `ld_added_group_access` or
`ld_removed_group_access`, and clears user group/course transients.

### Group -> course

The group-course relation is stored on the course post:

- `learndash_group_enrolled_{$group_id}` stores the timestamp when the course
  was added to the group.
- `learndash_group_{$group_id}_enrolled_at` is a historical timestamp kept for
  reporting and should not be casually deleted.

Use:

```php
ld_update_course_group_access( $course_id, $group_id );        // attach
ld_update_course_group_access( $course_id, $group_id, true );  // detach
```

For whole-list replacement, use the diff helpers:

- `learndash_set_group_enrolled_courses( $group_id, $course_ids )`
- `learndash_set_course_groups( $course_id, $group_ids )`

They compute additions/removals and call `ld_update_course_group_access()` for
each delta.

### Leader -> group

Group leaders are not the same as group members. A leader can administer a group
without being enrolled as a learner.

Use:

```php
ld_update_leader_group_access( $leader_user_id, $group_id );
ld_update_leader_group_access( $leader_user_id, $group_id, true );
```

Read with:

- `learndash_is_group_leader_user( $user_id )`
- `learndash_get_administrators_group_ids( $leader_user_id )`

## Reading group access

Use the API that matches the question:

- User belongs to group now:
  `learndash_is_user_in_group( $user_id, $group_id )`.
- User's direct and inherited group IDs:
  `learndash_get_users_group_ids( $user_id )`.
- Group's users:
  `learndash_get_groups_user_ids( $group_id )` or
  `learndash_get_groups_users( $group_id )`.
- Group's courses:
  `learndash_group_enrolled_courses( $group_id )`.
- Course's groups:
  `learndash_get_course_groups( $course_id )`.
- User gets a course through a group:
  `learndash_user_group_enrolled_to_course( $user_id, $course_id )`.
- Effective group-course access timestamp:
  `learndash_user_group_enrolled_to_course_from( $user_id, $course_id )`.

Do not decide group course access by checking one meta key. The user must be in
the group, the group must be attached to the course, and the group `Product`
model must allow access.

## Hierarchical groups

When hierarchical groups are enabled, `learndash_get_users_group_ids()` expands
the returned group IDs through validated child groups. That means:

- A direct meta scan can miss inherited access.
- `learndash_is_user_in_group()` can return true because the expanded group list
  contains the group.
- Custom SQL that only checks `learndash_group_users_{$group_id}` is not enough
  for access decisions.

If the task is about reporting direct memberships, say so explicitly. If the
task is about access, use LearnDash's group APIs.

## Managed Groups auto-enrollment in 5.1.8+

When `groups_autoenroll_managed` is enabled, a Group Leader's effective group
IDs are the union of:

- explicit learner memberships stored under `learndash_group_users_*`; and
- groups returned by `learndash_get_administrators_group_ids()` for that
  leader.

Before 5.1.8, the managed-group branch replaced the explicit memberships. Do
not reproduce that old behavior in a custom portal or cache. Keep leader-to-
group administration and learner-to-group membership as separate relations,
then use `learndash_get_users_group_ids()` when effective access is required.

The effective list is cached for one minute in
`learndash_user_groups_{$user_id}`. Pass `true` as the second argument to
`learndash_get_users_group_ids()` when the caller requires a fresh result.
`ld_update_group_access()` clears this cache, but in 5.1.9
`ld_update_leader_group_access()` does not. After changing a leader assignment,
hierarchy, or the Managed Groups setting, explicitly invalidate the transient
with `LDLMS_Transients::delete( 'learndash_user_groups_' . $user_id )` or use
the bypass until LearnDash's one-minute entry expires.

## Cache and transient caveats

Some older function signatures still expose `$bypass_transient`, but LearnDash
5.x has removed or narrowed transient behavior in several group helpers:

- `learndash_group_enrolled_courses()` no longer uses its old transient.
- `learndash_get_course_groups()` no longer uses its old transient.
- `learndash_get_groups_user_ids()` keeps the parameter for compatibility, but
  the current implementation does not rely on the old cached query.
- `ld_update_group_access()` clears `learndash_user_groups_{$user_id}` and
  `learndash_user_courses_{$user_id}`.

Do not add custom long-lived caches unless the invalidation story covers user
membership changes, group-course changes, hierarchy changes, and group leader
changes.

## Common mistakes to reject

- Writing `learndash_group_users_*` or `learndash_group_enrolled_*` meta
  directly instead of using the LearnDash helpers.
- Storing course IDs on the group post instead of attaching the group on the
  course postmeta.
- Assuming group leaders are enrolled learners.
- Removing historical `learndash_group_{$group_id}_enrolled_at`.
- Checking only direct membership when hierarchical groups can grant inherited
  access.
- Forgetting that group access can make `sfwd_lms_has_access()` true even when
  the user has no direct `course_{$course_id}_access_from`.

## Cross-references

- Use `learndash-course-access` for final course access decisions, expiration,
  direct course enrollment, and access-from dates.
- Use `learndash-woocommerce-access` when Woo products/orders/subscriptions
  grant LearnDash group access.
- Use `learndash-rest-api` for REST routes that manage group users, group
  courses, or user groups.
- Use `learndash-course-progress` for learner completion and group-course
  progress synchronization.

## References

Validated against LearnDash LMS 5.1.9 local source:

- `includes/ld-groups.php`
- `includes/course/ld-course-user-functions.php`
- `src/Core/Models/Product.php`
- `includes/rest-api/v2/class-ld-rest-groups-courses-controller.php`
- `includes/rest-api/v2/class-ld-rest-groups-users-controller.php`
- `includes/rest-api/v2/class-ld-rest-users-groups-controller.php`
- Official documentation: <https://developers.learndash.com/>

