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

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_conditionsaction 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
ifblock is the safety net. If Breakdance’sregisterConditionfunction isn’t available, or Milo’sManagerclass isn’t loaded, wereturnearly 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:
supportssays the condition works on both individual elements (element_display) and whole templates (templating). More on that second one later.slug,label, andcategoryare just identity and grouping. Thiscategoryis what puts everything under a tidy “Milo Subscriptions” heading in the builder.proOnlyis set tofalseon purpose. Breakdance marks third-party conditions as Pro-only by default and disables them on the free version; opting out lets these run everywhere.operandsis the is / is not toggle the user sees.valuesis 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:
- Grab the current user. If nobody’s logged in, the answer is just
false. - Work out which statuses count as a match. If the user picked “Any active subscription,” we treat both
activeandpending-cancelas 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. - Ask Milo for the user’s subscriptions in those statuses. If the result isn’t empty, they have one, so
$hasistrue. - 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
valuesfunction builds its dropdown from your actual store. It callswc_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
callbackdoes 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
- Edit any page in Breakdance.
- Click the element you want to gate, say a “Members Only” section.
- In the right-hand panel, open Settings → Conditions.
- Click Add Condition and open the category dropdown. You’ll see a new Milo Subscriptions group.
- 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.