Transform Learning with a Powerful Enterprise LMS

Learn more
WordPress LMS

Change "Add to cart" Text and URL for LearnDash Course Products

Two LearnDash WooCommerce filters change the archive button text and link for Course products. Snippets, the single-page filter and why AJAX buttons ignore your URL.

Learn more about this topic with AI

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

Illustration of a WooCommerce Course product button whose Add to cart text and link are changed with LearnDash filters

To change the "Add to cart" text or link on a LearnDash Course product in WooCommerce, hook the learndash_woocommerce_product_add_to_cart_text filter for the label and learndash_woocommerce_product_add_to_cart_url for the link. Both run inside the add-on's Course product class and receive the current value plus the product object, so you can change the button per product, per stock state or per learner.

Two things trip people up: these filters only cover the shop and archive button, not the single product page, and a purchasable Course product's archive button is usually an AJAX button that ignores its URL. Both are covered below, with two snippets you can adapt.

Checked against: the LearnDash LMS - WooCommerce Integration 2.0.0.1 source (with LearnDash 4.18.0.1) and the current WooCommerce source on GitHub, October 2026. LearnDash 5.x is current, and LearnDash 5.2.0 moved the WooCommerce add-on's functionality into core, as our LearnDash 5 upgrade notes explain. Before you ship, confirm the filter names still exist in the copy that runs on your site.

Where the two filters run

The filters live in src/App/Models/Products/Course.php, in the class LearnDash\WooCommerce\Models\Products\Course. That class extends WC_Product and backs the "Course" product type the add-on registers (type key course). If you have not used that product type before, start with the WooCommerce Course product type explained.

FilterArgumentsDefault value
learndash_woocommerce_product_add_to_cart_text$text, $product"Add to cart" when purchasable, "Read More" when not (for example, no price set)
learndash_woocommerce_product_add_to_cart_url$url, $productAn ?add-to-cart=<ID> link when purchasable and in stock, otherwise the product permalink

WooCommerce calls add_to_cart_text() and add_to_cart_url() when it renders the classic loop template (loop/add-to-cart.php) and the Product Button block, so the filters reach shop pages, category archives, related products and block-based product grids.

What these filters do not change

  • The single product page. The Course class does not override single_add_to_cart_text(). The add-on hooks woocommerce_course_add_to_cart to WooCommerce's woocommerce_simple_add_to_cart, so the product page uses the standard simple form and WooCommerce's own woocommerce_product_single_add_to_cart_text filter.
  • Simple or variable products linked to courses. You can link a course to a simple product or a variation too. Those use WooCommerce's classes, so the LearnDash filters never run for them.
  • Cart and checkout. Course products are sold individually and are virtual. Button text has no effect on quantity or enrolment.

Snippet 1: consistent labels on archive and product pages

This sets one label for buyable courses on both the archive and the product page, and a clearer label than "Read More" when a course cannot be bought. Put it in a small site plugin, not the theme. Not run on a live site; test on staging first.

<?php
/**
 * Plugin Name: AIOLMS Course Button Labels
 */

add_filter( 'learndash_woocommerce_product_add_to_cart_text', 'aiolms_course_archive_button_text', 10, 2 );
function aiolms_course_archive_button_text( $text, $product ) {
	if ( ! $product instanceof WC_Product ) {
		return $text;
	}
	return $product->is_purchasable()
		? __( 'Enrol now', 'aiolms' )
		: __( 'View course details', 'aiolms' );
}

add_filter( 'woocommerce_product_single_add_to_cart_text', 'aiolms_course_single_button_text', 10, 2 );
function aiolms_course_single_button_text( $text, $product ) {
	if ( $product instanceof WC_Product && 'course' === $product->get_type() ) {
		return __( 'Enrol now', 'aiolms' );
	}
	return $text;
}

Return plain text. WooCommerce escapes the label with esc_html() when it prints it, so HTML in the string shows up as literal tags.

Snippet 2: send existing learners to the course

A learner who already has access should not be offered the same course again. This version checks the course linked in the product's _related_course meta with sfwd_lms_has_access() and, when the learner has access, changes the button to "Go to course" pointing at the course permalink. It only acts when the product links exactly one course. Not run on a live site; test on staging first.

<?php
function aiolms_owned_course_id( $product ) {
	static $cache = array();

	if ( ! is_user_logged_in() || ! $product instanceof WC_Product || 'course' !== $product->get_type() ) {
		return 0;
	}
	$product_id = $product->get_id();
	if ( isset( $cache[ $product_id ] ) ) {
		return $cache[ $product_id ];
	}

	$course_ids = array_filter( array_map( 'absint', (array) get_post_meta( $product_id, '_related_course', true ) ) );
	$course_id  = ( 1 === count( $course_ids ) ) ? (int) reset( $course_ids ) : 0;

	if ( $course_id && ! sfwd_lms_has_access( $course_id, get_current_user_id() ) ) {
		$course_id = 0;
	}
	$cache[ $product_id ] = $course_id;
	return $course_id;
}

add_filter( 'learndash_woocommerce_product_add_to_cart_text', 'aiolms_owned_course_text', 20, 2 );
function aiolms_owned_course_text( $text, $product ) {
	return aiolms_owned_course_id( $product ) ? __( 'Go to course', 'aiolms' ) : $text;
}

add_filter( 'learndash_woocommerce_product_add_to_cart_url', 'aiolms_owned_course_url', 20, 2 );
function aiolms_owned_course_url( $url, $product ) {
	$course_id = aiolms_owned_course_id( $product );
	return $course_id ? get_permalink( $course_id ) : $url;
}

add_filter( 'woocommerce_loop_add_to_cart_args', 'aiolms_owned_course_loop_args', 20, 2 );
function aiolms_owned_course_loop_args( $args, $product ) {
	if ( isset( $args['class'] ) && aiolms_owned_course_id( $product ) ) {
		$args['class'] = trim( str_replace( 'ajax_add_to_cart', '', $args['class'] ) );
	}
	return $args;
}

Two side effects to know about. Administrators, and any role allowed to auto-enrol, pass sfwd_lms_has_access() for every course, so they see "Go to course" everywhere. And if a full-page cache serves the shop to logged-in users, one learner's button can be cached for everyone; most page caches skip logged-in sessions, but check yours.

Why a custom URL can be ignored

The Course class declares support for ajax_add_to_cart. With the WooCommerce option "Enable AJAX add to cart buttons on archives" switched on (WooCommerce > Settings > Products, under "Add to cart behaviour", on by default), a purchasable, in-stock Course product's loop button gets the ajax_add_to_cart class. WooCommerce's front-end script catches the click, cancels the link and posts the product ID to the cart, so your URL is never followed.

That is why snippet 2 removes the class through woocommerce_loop_add_to_cart_args. It only works for the classic loop template. The Product Button block works out its AJAX state in its own render code and renders a <button> with no link at all, so on a block theme a custom URL only applies when AJAX archive buttons are off or "Redirect to the cart page after successful addition" is on.

Moving off the old WooCommerce filters

Before version 2.0.0, code for course products hooked WooCommerce's generic woocommerce_product_add_to_cart_text and woocommerce_product_add_to_cart_url. The Course class still runs both through apply_filters_deprecated(), so old callbacks keep working for now, but WordPress logs a deprecation notice when WP_DEBUG is on.

  1. Search your theme and plugins for the two generic filter names.
  2. If a callback only targets course products, move it to the LearnDash filter and drop any product-type check you no longer need.
  3. If it targets every product type, leave it on the WooCommerce filter and skip Course products, so the label is not changed twice.

Test before release

  • A logged-out visitor, a learner without access and a learner with access, on the shop page, a category archive and the product page.
  • A Course product with no price, which should show your "not purchasable" label.
  • Both AJAX archive settings, and a block-based shop template if your theme uses one.
  • The debug log, for deprecation notices from older callbacks.

For broader context on how LearnDash sells through WooCommerce, see our LearnDash guide.

How we can help

If your store mixes Course products, subscriptions and group seats and the buttons, cart and enrolment logic need to agree, our LMS plugin development team can build and test the customisation as a maintainable site plugin.

Frequently asked questions

How do I change the Add to cart text for LearnDash courses in WooCommerce?

For products of the Course type, hook learndash_woocommerce_product_add_to_cart_text, which receives the text and the product object. For the button on the single product page, also hook WooCommerce's woocommerce_product_single_add_to_cart_text and check that the product type is course.

Why does my changed add to cart URL not work on the shop page?

When AJAX add to cart buttons on archives are enabled, a purchasable Course product's button carries the ajax_add_to_cart class and WooCommerce's script adds the item by AJAX instead of following the link. Remove that class with woocommerce_loop_add_to_cart_args, or turn AJAX archive buttons off.

Why does a Course product button say Read More?

The Course class returns "Read More" when the product is not purchasable, which usually means it has no price. The button then links to the product page instead of adding to cart.

Do these filters work on simple products linked to a LearnDash course?

No. They run only in the add-on's Course product class. Simple and variable products linked to courses use WooCommerce's own classes and filters.

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 developer documentation - LearnDash
  2. LearnDash LMS changelog - Nexcess / LearnDash
  3. WooCommerce source repository - WooCommerce
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