Transform Learning with a Powerful Enterprise LMS

Learn more
WordPress LMS

Snippet: Enable Free-Form Progression for Specific LearnDash Users

Keep a course linear for everyone except the users you pick: one filter, a user meta list or a group, an admin profile field, and the gates free form still applies.

Learn more about this topic with AI

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

Code snippet card for enabling LearnDash free-form progression for specific users

To have LearnDash disable linear progression for user accounts you choose while every other learner keeps it, hook the learndash_course_progression_enabled filter and return false when the current user is on your exception list. The course keeps its Linear setting; only the people you choose (testers, returning staff who already know the material, learners granted recognition of prior learning) can open steps in any order.

The filter has been in core since LearnDash 3.4.0 and wraps learndash_lesson_progression_enabled(), the function every template, shortcode and completion check calls to decide whether a course is linear. Checked against the LearnDash 4.18.0.1 source (October 2026); LearnDash 5.x is current, so confirm the filter on your version before you deploy.

How LearnDash decides a course is linear

Each course has a Course Progression setting with two options, Linear and Free form. It is stored as course_disable_lesson_progression in the course settings: empty means Linear, on means Free form. learndash_lesson_progression_enabled( $course_id ) reads that value and passes empty( $setting ) through the filter:

return apply_filters( 'learndash_course_progression_enabled', empty( $setting ), $course_id );

The filter receives two arguments: $enabled (true when the course is linear) and $course_id. There is no user argument. That matters for the snippet below, because the only user you can check is the one making the request.

The snippet

This version keeps the exception list in user meta (course IDs, with 0 meaning every course) and also lets you name one LearnDash group whose members skip linear order. Put it in a small site plugin rather than a theme so it survives theme changes. Not run on a live site; test on staging first.

<?php
/**
 * Plugin Name: AIOL LearnDash free-form progression for selected users
 */

defined( 'ABSPATH' ) || exit;

const AIOL_LD_FREE_FORM_META  = 'aiol_ld_free_form_courses';
const AIOL_LD_FREE_FORM_GROUP = 0; // Set a LearnDash group ID, or leave 0 to disable.

add_filter( 'learndash_course_progression_enabled', 'aiol_ld_free_form_for_user', 10, 2 );

function aiol_ld_free_form_for_user( $enabled, $course_id ) {
	if ( ! $enabled ) {
		return $enabled; // Course is already free form.
	}

	$user_id = get_current_user_id();
	if ( ! $user_id ) {
		return $enabled;
	}

	return aiol_ld_user_skips_linear( $user_id, absint( $course_id ) ) ? false : $enabled;
}

function aiol_ld_user_skips_linear( $user_id, $course_id ) {
	static $cache = array();
	$key = $user_id . ':' . $course_id;

	if ( isset( $cache[ $key ] ) ) {
		return $cache[ $key ];
	}

	$skip    = false;
	$courses = get_user_meta( $user_id, AIOL_LD_FREE_FORM_META, true );

	if ( is_array( $courses ) ) {
		$courses = array_map( 'absint', $courses );
		$skip    = in_array( 0, $courses, true ) || in_array( $course_id, $courses, true );
	}

	if ( ! $skip && AIOL_LD_FREE_FORM_GROUP ) {
		$skip = (bool) learndash_is_user_in_group( $user_id, AIOL_LD_FREE_FORM_GROUP );
	}

	$cache[ $key ] = $skip;
	return $skip;
}

The static cache is there because LearnDash calls learndash_lesson_progression_enabled() many times on a single course page: the course content list, navigation, the focus mode masthead and each Mark Complete check. learndash_is_user_in_group() is a core LearnDash function, so the group option needs no extra code.

Letting admins set the list from the user profile

You can write the meta with WP-CLI (wp user meta update) or add a field to the user edit screen. If you add a field, save it only for users who can edit other users. Otherwise a learner could open their own profile and grant themselves the exception.

add_action( 'edit_user_profile', 'aiol_ld_free_form_field' );

function aiol_ld_free_form_field( $user ) {
	if ( ! current_user_can( 'edit_users' ) ) {
		return;
	}
	$courses = (array) get_user_meta( $user->ID, AIOL_LD_FREE_FORM_META, true );
	printf(
		'<h2>%s</h2><p><input type="text" name="aiol_ld_free_form" value="%s"><br>%s</p>',
		esc_html__( 'LearnDash free-form progression', 'aiol' ),
		esc_attr( implode( ',', array_filter( $courses, 'strlen' ) ) ),
		esc_html__( 'Comma-separated course IDs. Use 0 for all courses.', 'aiol' )
	);
}

add_action( 'edit_user_profile_update', 'aiol_ld_free_form_save' );

function aiol_ld_free_form_save( $user_id ) {
	if ( ! current_user_can( 'edit_users' ) || ! isset( $_POST['aiol_ld_free_form'] ) ) {
		return;
	}
	check_admin_referer( 'update-user_' . $user_id );

	$raw = sanitize_text_field( wp_unslash( $_POST['aiol_ld_free_form'] ) );
	$ids = array_unique( array_map( 'absint', array_filter( array_map( 'trim', explode( ',', $raw ) ), 'strlen' ) ) );

	if ( $ids ) {
		update_user_meta( $user_id, AIOL_LD_FREE_FORM_META, array_values( $ids ) );
	} else {
		delete_user_meta( $user_id, AIOL_LD_FREE_FORM_META );
	}
}

The hooks are edit_user_profile and edit_user_profile_update, which only fire when someone edits another user's profile, not their own. update-user_{ID} is the nonce WordPress already prints on that form. Not run on a live site; test on staging first.

What free form changes, and what it does not

Returning false switches that user to the same behaviour as a course set to Free form. It does not remove every gate:

GateStill applies to excepted users?
Previous lesson or topic must be completeNo
Child topics and quizzes before the parent lesson is marked completeYes. In free-form mode learndash_can_complete_step() still blocks a parent with incomplete child steps
Lesson timer and assignment approvalYes
Video progressionYes
Drip release (days after enrolment or a specific date)Yes. Drip is a separate check
Course prerequisites and course pointsYes
Course access itselfYes. The user must still be enrolled

So an excepted user can jump to lesson 5, but cannot mark lesson 5 complete until its own topics and quizzes are done. Drip schedules are covered in our LearnDash drip content guide, and prerequisites in LearnDash course prerequisites.

Free form for a user vs the bypass filter

LearnDash has a second hook that looks similar: learndash_user_can_bypass, filtered inside learndash_can_user_bypass( $user_id, $context, $args ). It does receive a user ID, and with the learndash_course_progression context it is what lets administrators skip limits when the "Bypass Course limits" admin setting is on. It goes further than free form: when it returns true, learndash_process_mark_complete() forces completion and skips learndash_can_complete_step(), so timers, assignments and the video check stop applying too.

Use learndash_course_progression_enabled when the learner should still do the work, just in any order. Use the bypass filter for testers and reviewers who need to click through everything.

Gotchas

  • Current user only. When an admin marks a step complete for a learner, or a WP-CLI command or cron job processes progress, get_current_user_id() is the admin or 0, not the learner. The exception applies to the learner's own requests, which is where linear order is enforced.
  • Page caching. If a page cache serves logged-in users, course pages can show another user's lock state. Exclude logged-in LearnDash pages from full-page caching.
  • Already-free courses. The snippet returns early when $enabled is false, so it never forces a free-form course to become linear.
  • Custom templates. The legacy template's step navigation reads course_disable_lesson_progression directly in one place. The LD30 templates use the function. Check any template overrides in your theme.

Testing checklist

  1. Create two test subscribers enrolled in the same linear course. Add one to the exception list.
  2. In a private window, log in as the excepted user and open the last lesson directly. It should load without a "Please go back and complete the previous ..." message.
  3. Try Mark Complete on a lesson with incomplete topics. It should stay hidden.
  4. Log in as the second user and confirm the course is still linear.
  5. Remove the meta and confirm the first user is locked again.

For more on how LearnDash progression, access and completion fit together, start with the LearnDash guide.

How we can help

If you need progression rules that go beyond one filter, such as per-cohort sequencing or credit for prior learning imported from an HR system, our LMS plugin development team builds and maintains them as small, upgrade-safe plugins.

Frequently asked questions

How do I turn off linear progression for one user in LearnDash?

Add a callback to the learndash_course_progression_enabled filter that returns false when get_current_user_id() is on your exception list, for example a user meta list of course IDs. The course stays linear for everyone else.

Does free-form progression skip LearnDash drip content?

No. Drip release by days or by date is a separate check, so lessons stay locked until their drip date even for users with free-form progression.

What is the difference between free form and bypassing course limits?

Free form only removes the requirement to complete steps in order. The bypass used for admins, filtered by learndash_user_can_bypass, also forces step completion past timers, assignments and video progression.

Can I make LearnDash progression free form for a group?

Yes. Inside the same filter, check learndash_is_user_in_group( $user_id, $group_id ) and return false for members of that group.

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. learndash_course_progression_enabled - LearnDash developer documentation
  2. learndash_user_can_bypass - LearnDash developer documentation
  3. edit_user_profile_update hook - WordPress.org
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