How to Change the WooCommerce Subscriptions Product Price String

Quick Summary
- Filter woocommerce_subscriptions_product_price_string: it passes the default string, the product and an $include array of flags, and the string you return is shown instead.
- Since WooCommerce Subscriptions 9.0 the sign-up fee and free trial already show on their own lines, so check your version before adding code, and test the filter on a staging copy.
- The filter changes product prices and the classic cart; the Cart and Checkout blocks build their text in JavaScript and need registerCheckoutFilters() instead.
To change the price text that WooCommerce Subscriptions shows for a subscription product, filter woocommerce_subscriptions_product_price_string. Your function receives the default string, the product and an $include array of flags, and WooCommerce shows whatever it returns instead. Add it to your child theme’s functions.php. If all you want is the sign-up fee on its own line, update the plugin first: since version 9.0, product pages already show the sign-up fee and free trial on their own lines.
What changed in WooCommerce Subscriptions 9?
According to the plugin’s changelog:
- 9.0.0 (June 2026) moved the free trial and sign-up fee onto their own lines below the price, instead of appending them to the price text. It also built All Products for Subscriptions into the plugin, so any product can be sold on a subscription plan, and turned the older “Simple subscription” and “Variable subscription” product types off by default.
- 9.0.1 gave the classic shortcode cart and checkout the same layout: the trial and sign-up fee on their own labeled lines.
- 9.1.0 stopped filtering
woocommerce_cart_product_subtotal, so the cart’s subtotal column no longer goes through the price string.
WooCommerce Subscriptions is a paid plugin and its current code isn’t public. The code in this post was checked against the last public version of its core library, subscriptions-core 8.2.0, which WooCommerce Subscriptions 7.4 bundled before Automattic archived the repository in May 2025. The filter is still listed in WooCommerce’s Subscriptions filter reference, but on version 9 and later, test the result on a staging copy first. In particular, check that the sign-up fee doesn’t now show twice.
What does the filter receive?
WC_Subscriptions_Product::get_price_string() builds the text and passes it through the filter with three arguments:
$subscription_string: the default HTML, such as the price followed by<span class="subscription-details">for 6 months and a $500.00 sign-up fee</span>.$product: the product object.$include: flags that say what the string should contain:tax_calculation,subscription_price,subscription_period,subscription_length,sign_up_feeandtrial_length. When the caller already has a formatted price, such as the product page or the classic cart, it passes it in$include['price'], with the sale price and your tax display setting applied.
A second filter, woocommerce_subscriptions_product_price_string_inclusions, runs before the string is built and receives $include and the product. To remove one part of the default text, change its flag there instead of rebuilding the whole string:
// Leave the free trial out of the default price string.
add_filter( 'woocommerce_subscriptions_product_price_string_inclusions', 'my_subs_price_string_inclusions', 10, 2 );
function my_subs_price_string_inclusions( $include, $product ) {
$include['trial_length'] = false;
return $include;
}The default price string
Let’s say we have 3 types of Subscription with a Subscription fee of $500.00
- Monthly
- Annually
- 2-Pay [Basically have to pay in every 6 Months]
With the WooCommerce Subscriptions plugin, the price shows like this:
“$950.00 for 6 months and a $500.00 sign-up fee“
What I’d like to do is change the price string so it looks more professional. For example, we can show the sign-up fee text on the next line.
A quick example
A code snippet I found on Stack Overflow shows the simplest use of the filter: it ignores the default string and returns a new sentence built from the sign-up fee, the trial length and period, and the subscription price:
add_filter( 'woocommerce_subscriptions_product_price_string', 'my_subs_price_string', 10, 3 );
function my_subs_price_string( $subscription_string, $product, $include ) {
/****
var_dump($product); Various variables are available to us
****/
return 'An initial easy payment of ' . wc_price( WC_Subscriptions_Product::get_sign_up_fee( $product ) ) .
', a ' . WC_Subscriptions_Product::get_trial_length( $product ) . ' ' . WC_Subscriptions_Product::get_trial_period( $product ) .
' trial of the product, then an outright purchase of ' . wc_price( WC_Subscriptions_Product::get_price( $product ) );
}It only reads well for products that have both a trial and a sign-up fee. The original snippet read these values as properties, such as $product->subscription_price, a pattern from before WooCommerce 3.0. In subscriptions-core 8.2.0 that still returned the value, but each access raised a “Product properties should not be accessed directly with WooCommerce 3.0+” deprecation notice. The static WC_Subscriptions_Product getters used here, get_price(), get_sign_up_fee(), get_trial_length() and get_trial_period(), return the same values without the notices.
My version: price per period, fee on its own line
I then modified this code for my needs. It was first shared as a GitHub Gist and is updated here to use the getters, the price WooCommerce Subscriptions passes in, and the billing interval:
// Update the subscription price string.
add_filter( 'woocommerce_subscriptions_product_price_string', 'my_subs_price_string', 10, 3 );
function my_subs_price_string( $subscription_string, $product, $include ) {
// var_dump( $include ); // Uncomment to see what WooCommerce Subscriptions passes in.
// The price as already formatted for this spot: product page, cart price or cart subtotal.
$price = isset( $include['price'] ) ? $include['price'] : wc_price( WC_Subscriptions_Product::get_price( $product ) );
$interval = (int) WC_Subscriptions_Product::get_interval( $product );
$period = WC_Subscriptions_Product::get_period( $product );
$length = (int) WC_Subscriptions_Product::get_length( $product );
$sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product );
$lengths = wcs_get_subscription_ranges( $period ); // "1 month", "6 months"...
if ( $length > 0 && $length === $interval ) {
// One payment covers the whole subscription, for example "$950.00 / for 6 months".
$string = $price . ' / for ' . $lengths[ $length ];
} else {
// A repeating payment, for example "$250.00 / month" or "$450.00 / 3 months".
$string = $price . ' / ' . wcs_get_subscription_period_strings( $interval, $period );
if ( $length > 0 ) {
// For example "$10.00 / month for 12 months".
$string .= ' for ' . $lengths[ $length ];
}
}
// The sign-up fee goes on its own line, only when there is one.
if ( ! empty( $include['sign_up_fee'] ) && $sign_up_fee > 0 ) {
// In the cart subtotal, the fee arrives already formatted for the quantity.
if ( is_bool( $include['sign_up_fee'] ) ) {
$fee = wc_price( wc_get_price_to_display( $product, array( 'price' => $sign_up_fee ) ) );
} else {
$fee = $include['sign_up_fee'];
}
$string .= '<br>' . $fee . ' sign-up fee <i>(one-time fee)</i>';
}
return $string;
}How it works
- It starts from
$include['price'], the price WooCommerce Subscriptions already formatted for the place where the string appears, so sale prices, your tax display setting and the cart quantity are kept. It formats the product’s own price only when it receives no price. - The billing interval is how often a payment is taken, such as every 3 months, and the length is how long the subscription runs, 0 when it runs until cancelled. When one payment covers the whole length, as with the 2-Pay product, the string reads “$950.00 / for 6 months”. Otherwise it shows the price per interval, and the length when there is one.
wcs_get_subscription_period_strings()andwcs_get_subscription_ranges()are WooCommerce Subscriptions functions that return translated period texts such as “month”, “3 months” or “6 months”.- The sign-up fee goes on its own line only when the product has a fee and
$include['sign_up_fee']asks for it. On the product page that flag istrue, andwc_get_price_to_display(), a WooCommerce function, applies your shop’s tax display setting to the fee. In the cart subtotal of versions before 9.1, the flag held the fee already formatted for the quantity, which is why the code checksis_bool(). - The code leaves free trials out. If your products have one, add it with
get_trial_length()andget_trial_period(), as the first snippet does.
Both snippets use the same function name, so add one or the other, not both. The commented-out var_dump() line prints what the filter receives; remove it once you have what you need.
Run with PHP 8.2 against stubbed product getters and the real period functions from subscriptions-core 8.2.0, the function printed “$250.00 / month” for a monthly product, “$950.00 / for 6 months” for the 2-Pay product, “$450.00 / 3 months” for a product billed every 3 months and “$10.00 / month for 12 months” for a monthly product that runs 12 months, each followed by the fee line when the product had one. The earlier version of this code printed “$450.00 / month” and “$10.00 / for 12 months” for the last two, and showed the single price in the cart subtotal whatever the quantity.
Where does the new string show?
- Product pages and product lists. Subscription products build their price HTML with
get_price_string(), so the filter applies wherever WooCommerce printsget_price_html(). That includes the Product Price block in block themes, which callsget_price_html()in WooCommerce 11.1. - The classic cart. In subscriptions-core 8.2.0, the cart’s price column and subtotal column also went through the filter. Since 9.1.0, the subtotal column doesn’t.
- Not the Cart and Checkout blocks. There, WooCommerce Subscriptions builds the price text in JavaScript from the Store API’s cart data. In subscriptions-core 8.2.0 it did this with the blocks’ checkout filters
subtotalPriceFormat,cartItemPriceandsaleBadgePriceFormat. To change the text there, register your own filter withregisterCheckoutFilters(), as described in WooCommerce’s cart line item filters. The filtered value must still contain the<price/>placeholder. - Not recurring totals, My Account or emails. Those strings describe the cart or a subscription, not a product. They come from
wcs_price_string()and its own filter,woocommerce_subscription_price_string.
Before and after
Before:

After:

The cart with the new price string:

These screenshots are from the classic cart and product page of the time, before WooCommerce Subscriptions 9.
If your subscription store needs more than a new price string, I set up and customize subscriptions as part of my WooCommerce development work.
Related articles
- replacing the variable price range with a Starting At price: another filter that rewrites how a price is shown
- adding the amount saved under sale prices: extra text in the price HTML with one filter
