WooCommerce Shipping Methods by User Role
Show different WooCommerce shipping methods by user role: freight for wholesale, parcel for retail, plus guest-checkout pitfalls and a safe test plan.

A store serving both retail and trade customers has two different logistics realities. The consumer wants a tracked parcel and a delivery date. The wholesale buyer wants a pallet on a tail-lift truck, possibly on their own carrier account, and would find "€6.95 standard post" for 40 boxes meaningless. Offering both lists to everybody produces wrong orders in both directions, which is why it is worth setting WooCommerce shipping methods by user role.
Decide the matrix before you touch code
Write the matrix down first — three roles across the top, your methods down the side, and a yes/no in each cell. Typical outcome:
| Method | Guest | Customer | Wholesale |
|---|---|---|---|
| Flat rate parcel | Yes | Yes | No |
| Free shipping over threshold | Yes | Yes | No |
| Local pickup | Yes | Yes | Yes |
| Pallet freight | No | No | Yes |
| Collect on own account | No | No | Yes |
The interesting column is the first one. Roles do not exist for guests, so every rule needs an explicit answer for a visitor who is not logged in.
The guest question
Two defensible policies:
- Guests are retail. They see the public parcel methods; freight stays behind login. This is right for almost every store and it is the safe default — a guest can never select a rate you cannot invoice.
- No guest checkout at all. For pure B2B stores, require an account and let the role decide everything. It costs you consumer conversions, so only do this if you have no consumer business.
Either way, do not leave freight visible to guests "because they might be a trade customer". They will use it, and you will be moving a pallet for whatever the placeholder cost was.
Implementing the rule
Shipping rates pass through `woocommerce_package_rates` just before the cart and checkout render, so the role check lives there:
```php add_filter( 'woocommerce_package_rates', function ( $rates, $package ) { $user = wp_get_current_user(); $roles = (array) $user->roles;
$is_wholesale = in_array( 'wholesale_customer', $roles, true );
foreach ( array_keys( $rates ) as $rate_id ) { $is_freight = str_starts_with( $rate_id, 'flat_rate' ) && str_contains( $rate_id, 'freight' );
if ( $is_wholesale && ! $is_freight && ! str_starts_with( $rate_id, 'local_pickup' ) ) { unset( $rates[ $rate_id ] ); // trade buyers: freight or pickup only }
if ( ! $is_wholesale && $is_freight ) { unset( $rates[ $rate_id ] ); // retail and guests: never freight } }
return $rates; }, 10, 2 ); ```
Notes from live stores. A user can hold several roles, so check with `in_array` rather than reading `$roles[0]`. Administrators usually hold no customer role, so test as a real wholesale account, not as yourself. And WooCommerce caches rates per session: after logging in, the previously calculated rates may still be in play, so recalculate.
Login and logout are the dangerous moments
The bug that reaches production most often is a customer who fills a cart as a guest, logs in at checkout, and keeps a shipping method their role is no longer entitled to. WooCommerce stores the chosen method in the session; if your filter removes it, the stored choice can persist as a stale selection.
Handle it explicitly: on login, mark shipping for recalculation and clear the chosen method when it is no longer in the rate list. WooCommerce then falls back to the first valid rate, which is always better than an order placed on a rate that no longer exists.
Keeping the rules maintainable
Role logic grows: a second wholesale tier, a distributor role, a staff role with pickup only. At that point PHP conditionals stop being reviewable. Our WooCommerce Shipping Gateway Per Product plugin runs the same filter from rules stored as data, so the matrix above stays visible in wp-admin — which also means your fulfilment team can read it without asking a developer.
Role rules also rarely travel alone. Trade customers usually get their own prices and payment terms too; if that is where you are heading, our guides on role-based pricing and payment gateways by user role cover the two rules that pair with this one.
Test plan
- Guest cart — retail methods only, no freight.
- Retail customer logged in — same as guest.
- Wholesale customer — freight and pickup only.
- Guest fills a cart, then logs in as wholesale — the selected method must change to a valid one.
- Wholesale customer logs out mid-session — freight must disappear.
- An address outside the freight zone — the zone removes freight before the role rule ever runs.
Finish with one real order per role. Role-based logistics failures show up as unfulfillable orders rather than as errors, so the only reliable check is placing the order.
FAQ
- How do I show different shipping methods to different user roles in WooCommerce?
- Filter woocommerce_package_rates, read the current user's roles with wp_get_current_user(), and unset the rates that role should not see. Roles only exist for logged-in customers, so decide what guests get first.
- What shipping methods should guests see?
- Treat guests as retail. Show the public parcel methods and keep freight or account-only rates behind login, otherwise anyone can select a wholesale rate you cannot invoice.
- Can wholesale customers get free freight above an order value?
- Yes. Combine the role check with the package subtotal: keep the freight rate only when the role matches and the cart total clears your threshold, and set the rate cost to zero above it.
- Do role-based shipping rules work with shipping zones?
- They stack. Zones decide which methods are geographically possible, the role rule then removes the ones this customer is not entitled to. Configure the zone first, then filter.
- How do I stop a customer losing their selected shipping method after logging in?
- Recalculate shipping on login and clear the chosen method when it is no longer available, so WooCommerce falls back to the first valid rate instead of holding a rate the role cannot use.
This article is part of our WooCommerce shipping topic hub, where the related plugins and guides live together.


