Building Role-Based Pricing in WooCommerce Without a Plugin — ACF Pro + Custom Hooks
Every few months I see the same thread: someone asking which role-based pricing plugin to use for WooCommerce. The answers are always the same handful of plugin names — and they’re all overkill for what most agency builds actually need.
For the Diversified Air catalog build, I needed five customer tiers with completely different prices on 500+ products, managed by non-technical staff. Here’s the architecture I built using ACF Pro and WooCommerce’s own hook system — no pricing plugin required.
Why Skip the Plugin?
Most role-based pricing plugins add their own product metaboxes, their own import compatibility quirks, and their own filter priorities that conflict with WooCommerce’s core pricing hooks. When you’re building a complex site that also needs WP All Import and custom ACF fields, these conflicts become a real problem.
More importantly: if you understand WooCommerce’s pricing hooks and ACF’s options API, you can build exactly what you need in about 150 lines of PHP that you fully control.
The Architecture
The system has three components:
- ACF Options Page — a pricing matrix where admins set percentage modifiers per user role per product category
- A pricing hook — filters
woocommerce_product_get_priceandwoocommerce_product_get_regular_priceto return the role-adjusted price - A display helper — shows the correct price in the catalog and cart based on current user role
Setting Up the ACF Options Page
First, register an options page in your functions.php or dedicated includes file:
if( function_exists('acf_add_options_page') ) {
acf_add_options_page(array(
'page_title' => 'Pricing Settings',
'menu_title' => 'Pricing Matrix',
'menu_slug' => 'pricing-matrix',
'capability' => 'manage_options',
'position' => 58,
));
}
Then create an ACF field group targeting that options page. The field structure is a repeater: one row per user role, with sub-fields for each product category modifier (stored as a percentage, e.g. 0.85 = 15% discount from list price).
The Pricing Hook
This is where the magic happens. WooCommerce fires these filters when it needs to display or calculate a price:
add_filter('woocommerce_product_get_price', 'ffweb_role_price', 10, 2);
add_filter('woocommerce_product_get_regular_price', 'ffweb_role_price', 10, 2);
function ffweb_role_price( $price, $product ) {
if( is_admin() || ! is_user_logged_in() ) return $price;
$user = wp_get_current_user();
$role = $user->roles[0] ?? 'customer';
$categories = wc_get_product_term_ids( $product->get_id(), 'product_cat' );
$modifier = ffweb_get_modifier( $role, $categories );
return $modifier ? round( (float)$price * $modifier, 2 ) : $price;
}
The ffweb_get_modifier() function reads from the ACF options page and returns the right decimal multiplier for the current role and product category combination.
The key insight: keep the pricing logic in one function, make it readable, and add caching with a transient so it doesn’t hit the options table on every product in a catalog loop.
Making it Admin-Manageable
The ACF repeater on the options page means your client’s sales team can adjust the multiplier for any role/category combination without touching code. Log into WordPress, go to Pricing Matrix, change the Dealer modifier for HVAC Coils from 0.78 to 0.75, hit Save — done.
This is the part that sells the approach to agency clients. The tech is invisible; the control is obvious.
Caveats
A few things to know before you implement this on your own build:
- Sale prices need their own filter:
woocommerce_product_get_sale_price - Variable products need additional handling for variation prices
- If you’re using WP All Import, test your import after adding the pricing hooks — some import sequences trigger pricing hooks unexpectedly
- Cache the options call with a short-lived transient if you have large catalogs in AJAX loops