Tips

Milo Subscriptions, meet Breakdance: restrict content by subscription

This one started in our support inbox. A customer was building client sites in Breakdance and wrote in with a simple question: “Using WooCommerce Subscriptions, we’re able to set conditions in Breakdance based on ‘User Subscriptions’ to display or hide content. Are we able to do this with your plugin somehow?” Out of the box, […]

By Rémi Corson · June 3, 2026 · 8 min read

Tips

This one started in our support inbox.

A customer was building client sites in Breakdance and wrote in with a simple question: “Using WooCommerce Subscriptions, we’re able to set conditions in Breakdance based on ‘User Subscriptions’ to display or hide content. Are we able to do this with your plugin somehow?”

Out of the box, the answer was “not yet.” But it turned out to be an easy gap to close, so we wrote a snippet for them. This post is that snippet, cleaned up and explained, so anyone can do the same today.

Once it’s in place, two new conditions show up in Breakdance:

  • User Subscription Status: is / is not Active, On hold, Expired, or “any active subscription”
  • Subscribed to Product: is one of / none of / all of your subscription products

They sit right alongside Breakdance’s built-in conditions, like logged-in status or user role. No shortcodes, no custom fields, just pick an element and gate it.

The snippet, explained bit by bit

Save the three blocks below, stacked in order, as a single file in wp-content/plugins/ (for example milo-breakdance-conditions.php) and activate it like any plugin. You can also paste the body into your theme’s functions.php or a Code Snippets plugin.

It’s inert unless both Breakdance and Milo Subscriptions are active, so it’s safe to leave it installed.

Block 1: the header, the hook, and a safety check

<?php
/**
 * Plugin Name: Milo Subscriptions: Breakdance Conditions
 * Description: Adds "User Subscription" display conditions to Breakdance, powered by Milo Subscriptions.
 * Version:     1.0.0
 */

defined( 'ABSPATH' ) || exit;

add_action(
	'breakdance_register_template_types_and_conditions',
	'milo_bd_register_conditions'
);

function milo_bd_register_conditions() {

	if (
		! function_exists( '\Breakdance\Themeless\registerCondition' )
		|| ! class_exists( '\Milo\Subscriptions\Manager' )
	) {
		return;
	}

What’s happening here:

  • The plugin header is standard WordPress boilerplate. defined( 'ABSPATH' ) || exit; just stops anyone from loading the file directly in a browser.
  • Breakdance fires the breakdance_register_template_types_and_conditions action when it’s collecting the list of conditions to show in the builder. We hook our function onto that moment. This is the one hook you need to know.
  • The if block is the safety net. If Breakdance’s registerCondition function isn’t available, or Milo’s Manager class isn’t loaded, we return early and do nothing. That’s what makes the file safe to leave installed even if you deactivate one of the two plugins later.

Note we don’t close the function yet. The two conditions go inside it.

Block 2: the “User Subscription Status” condition

	// Condition 1 — User Subscription Status
	\Breakdance\Themeless\registerCondition( array(
		'supports'         => array( 'element_display', 'templating' ),
		'availableForType' => array( 'ALL' ),
		'slug'             => 'milo-user-subscription-status',
		'label'            => 'User Subscription Status',
		'category'         => 'Milo Subscriptions',
		// Breakdance flags third-party conditions as Pro-only by default, which
		// disables them on the free version. Opting out makes them work on both.
		'proOnly'          => false,
		'operands'         => array( 'is', 'is not' ),
		'valueInputType'   => 'dropdown',
		'values'           => function () {
			return array( array(
				'label' => 'Subscription',
				'items' => array(
					array( 'text' => 'Any active subscription', 'value' => 'any-active' ),
					array( 'text' => 'Active',                  'value' => 'active' ),
					array( 'text' => 'On hold',                 'value' => 'on-hold' ),
					array( 'text' => 'Pending',                 'value' => 'pending' ),
					array( 'text' => 'Pending cancellation',    'value' => 'pending-cancel' ),
					array( 'text' => 'Cancelled',               'value' => 'cancelled' ),
					array( 'text' => 'Expired',                 'value' => 'expired' ),
				),
			) );
		},
		'callback'         => function ( $operand, $value ) {
			$user_id = get_current_user_id();
			$has = false;
			if ( $user_id ) {
				// "Any active subscription" = Active OR Pending-cancellation
				// (still in a paid period). A specific status matches only itself.
				$statuses = ( 'any-active' === $value )
					? array( 'active', 'pending-cancel' )
					: array( (string) $value );
				$has = ! empty( \Milo\Subscriptions\Manager::get_user_subscriptions( $user_id, $statuses ) );
			}
			return ( 'is not' === $operand ) ? ! $has : $has;
		},
	) );

registerCondition takes one big array. Read it top to bottom and it’s mostly self-describing:

  • supports says the condition works on both individual elements (element_display) and whole templates (templating). More on that second one later.
  • slug, label, and category are just identity and grouping. This category is what puts everything under a tidy “Milo Subscriptions” heading in the builder.
  • proOnly is set to false on purpose. Breakdance marks third-party conditions as Pro-only by default and disables them on the free version; opting out lets these run everywhere.
  • operands is the is / is not toggle the user sees.
  • values is a function that returns the dropdown options. It runs when Breakdance draws the condition, so the list is always current.

The interesting part is callback. This is the function that decides true or false when the page renders:

  1. Grab the current user. If nobody’s logged in, the answer is just false.
  2. Work out which statuses count as a match. If the user picked “Any active subscription,” we treat both active and pending-cancel as a pass, because someone who cancelled but is still inside a paid period still has access. Otherwise, we match only the exact status they chose.
  3. Ask Milo for the user’s subscriptions in those statuses. If the result isn’t empty, they have one, so $has is true.
  4. Finally, flip the result if the user chose “is not.”

That Manager::get_user_subscriptions() call is the entire bridge. It’s Milo’s public method, and it does the heavy lifting so the snippet doesn’t have to.

Block 3: the “Subscribed to Product” condition

	// Condition 2 — Subscribed to Product
	\Breakdance\Themeless\registerCondition( array(
		'supports'         => array( 'element_display', 'templating' ),
		'availableForType' => array( 'ALL' ),
		'slug'             => 'milo-subscribed-to-product',
		'label'            => 'Subscribed to Product',
		'category'         => 'Milo Subscriptions',
		// Same opt-out as above, so this condition works on free Breakdance too.
		'proOnly'          => false,
		'operands'         => array( 'is one of', 'is none of', 'is all of' ),
		'values'           => function () {
			$products = wc_get_products( array(
				'type'   => array( 'subscription', 'variable-subscription' ),
				'status' => 'publish',
				'limit'  => -1,
			) );
			return array( array(
				'label' => 'Subscription products',
				'items' => array_map( function ( $p ) {
					// Value MUST be a string. Breakdance validates condition
					// values as strings and rejects the payload otherwise.
					return array( 'text' => $p->get_name(), 'value' => (string) $p->get_id() );
				}, $products ),
			) );
		},
		'callback'         => function ( $operand, $value ) {
			$selected = array_map( 'intval', (array) $value );
			$owned    = array();
			$user_id  = get_current_user_id();
			if ( $user_id ) {
				$subs = \Milo\Subscriptions\Manager::get_user_subscriptions( $user_id, array( 'active', 'pending-cancel' ) );
				foreach ( $subs as $sub ) {
					foreach ( $sub->get_items() as $item ) {
						$owned[] = (int) $item->get_product_id();
						if ( $item->get_variation_id() ) {
							$owned[] = (int) $item->get_variation_id();
						}
					}
				}
			}
			$matches = array_map( function ( $id ) use ( $owned ) {
				return in_array( $id, $owned, true );
			}, $selected );

			if ( 'is one of'  === $operand ) { return in_array( true, $matches, true ); }
			if ( 'is none of' === $operand ) { return ! in_array( true, $matches, true ); }
			if ( 'is all of'  === $operand ) { return ! empty( $matches ) && ! in_array( false, $matches, true ); }
			return false;
		},
	) );
}

Same shape as the first condition, with two differences worth pointing out:

  • The values function builds its dropdown from your actual store. It calls wc_get_products() for published subscription products (both simple and variable) and lists them. There’s a small but important detail in the comment: the value has to be cast to a string with (string), because Breakdance validates condition values as strings and will reject the whole thing if you hand it a number.
  • The callback does a bit more. It collects every product (and variation) the user is actively subscribed to, then checks the selected products against that list. The three operands then mean what they say: is one of passes if at least one matches, is none of passes if none do, and is all of passes only if every selected product is owned.

The final } closes the milo_bd_register_conditions function we opened back in Block 1. Stack the three blocks in order and that’s the complete plugin.


Using it in Breakdance

  1. Edit any page in Breakdance.
  2. Click the element you want to gate, say a “Members Only” section.
  3. In the right-hand panel, open Settings → Conditions.
  4. Click Add Condition and open the category dropdown. You’ll see a new Milo Subscriptions group.
  5. Pick User Subscription Status → is → Any active subscription.

Now that section only renders for customers with a live subscription. Everyone else never even receives the content. It’s removed server-side, not just hidden with CSS.

Want to gate by a specific plan instead? Use Subscribed to Product → is one of → [your product]. That’s the one for “Gold tier” content, course modules, or download areas tied to a particular subscription.

Bonus: gate a whole template

Because the conditions also support templating, you can apply them to an entire Breakdance Template, not just individual elements. Build a “Members Area” template, set its condition to User Subscription Status is Active, and the whole layout becomes subscriber-only.


A couple of things to know

  • It works at render time. The condition is evaluated when the page is built, so it always reflects the visitor’s current subscription. No caching gotchas on the logic itself, though you should still exclude gated pages from full-page caches.
  • Logged-out visitors always fail the “active” checks, which is exactly what you want for a paywall.
  • Performance: Each check runs a quick lookup of the user’s subscriptions. On a page with a lot of gated elements that’s fine, but the native integration we’re building will memoise it down to a single query per request.

Which builder should we do next?

This snippet came from one support request, and it turned into something every Breakdance user can use.

With that being said, which page builder do you reach for, and which one would you most like to see Milo support next? If you’re using something that isn’t Breakdance, Elementor, or Bricks, tell us. We’d like to know what to prioritise.

Try the engine, it costs nothing

Free on WordPress.org, and one percent only when you get paid.

See pricing

Keep reading