How to Change a User Role After a WooCommerce Purchase

You can change a WordPress user role automatically when a WooCommerce order reaches “Completed” status, either with a visual workflow builder that takes about five minutes to configure, or with a code snippet targeting the woocommerce_order_status_completed hook. Neither approach requires a dedicated membership plugin.

This tutorial covers the full picture: assigning a role tied to a specific product, handling guest purchasers who have no WordPress account yet, and revoking a role when an order is refunded or cancelled. Those last two scenarios are the ones most guides skip, and they are where broken setups cause the most support tickets.

Work through the options in the order they appear here. The no-code path takes less time and gives you execution logs from day one. The code path gives you full control with zero additional plugins, but requires a place to store snippets safely.

What You Are Actually Trying to Solve

The motivating use case is almost always one of these three things:

  • Membership access: a customer buys a “Premium Member” product and should move from the default customer role to a custom premium_member role that unlocks restricted content.
  • VIP tier: a customer spends over a threshold (say $200 in a single order) and earns a vip role that unlocks a wholesale price list.
  • Course or community access: a customer buys a course bundle and should be elevated to a role that a LearnDash group or BuddyBoss community recognises as enrolled.

All three follow the same logic: an order event fires, you check a condition, and you call WordPress’s wp_update_user() or WP_User::set_role() with a new role slug. The difference is which condition you check and what happens when the purchase is reversed.

A role change with no revocation path is a promise you will eventually break. Build the cancellation branch at the same time you build the assignment branch.

Option A: No-Code Workflow (Recommended Starting Point)

Krom Automation’s free tier includes both the Order Completed trigger and the Change User Role action, along with conditional branching to filter by product ID. Installation takes under two minutes from the WordPress.org plugin directory. If you want to understand how the builder works before you start, the triggers, actions, and workflows overview explains the mental model clearly.

Step 1: Install and Open the Workflow Builder

Go to Plugins Add New, search for “Krom Automation”, install and activate. A new Krom Automation menu appears in your sidebar.

Click it, then click New Workflow. You will land on a drag-and-drop canvas with an empty start node.

Step 2: Set the Trigger to “Order Completed”

Click the start node and choose WooCommerce Order Completed from the trigger panel on the right. This fires once per order, after WooCommerce moves the order status to completed.

It does not fire for processing or pending, which matters if your product is digital and auto-completes immediately. Refer to the free triggers reference for the exact data fields each trigger exposes.

Step 3: Add a Condition to Filter by Product

Click the + button below the trigger node and add a Condition block. Set it to check whether Order Line Items Product ID contains your target product’s ID. The canvas splits into a Yes path and a No path.

Everything on the Yes path runs only for orders that contain that product. The conditions and branching documentation shows every available operator, including “contains any of” for matching multiple product IDs at once.

Step 4: Add the “Change User Role” Action

On the Yes path, add an action node. Choose User Change User Role. Set the User field to the merge tag {{order.customer_id}} and the New Role field to your target role slug, for example premium_member.

Save. The merge tags documentation lists every dynamic variable available from the Order Completed trigger.

Step 5: Test Before Going Live

Click the Simulate button in the top toolbar. The simulator runs the workflow with a real order ID you supply, shows you what each step would do, and exits without writing any changes to the database.

This dry-run capability catches configuration mistakes before they touch live user data. The workflow simulator documentation explains how to supply test data for each trigger type.

Once the simulation shows the correct role slug in the action output, toggle the workflow to Active and place a test order. Check the execution log under Krom Automation Logs to confirm the action ran and the user record updated.

Option B: Code Snippet

If you prefer not to add a plugin, the following snippet handles the same assignment. Add it to a site-specific plugin or use a snippet manager like WPCode. Never paste custom PHP into functions.php on a theme you did not build, because a theme update will erase it.

add_action( 'woocommerce_order_status_completed', 'assign_role_on_product_purchase' );

function assign_role_on_product_purchase( $order_id ) {
    $order      = wc_get_order( $order_id );
    $user_id    = $order->get_customer_id();
    $target_ids = array( 123, 456 ); // Replace with your product IDs

    if ( ! $user_id ) {
        return; // Guest checkout, see the guest section below
    }

    foreach ( $order->get_items() as $item ) {
        if ( in_array( $item->get_product_id(), $target_ids, true ) ) {
            $user = new WP_User( $user_id );
            $user->set_role( 'premium_member' );
            break;
        }
    }
}

Use set_role() not add_role() here. The set_role() method replaces the user’s primary role. The add_role() method stacks an additional role on top.

Most membership setups want replacement, because a user with both customer and premium_member roles creates ambiguous capability checks. If your access control explicitly relies on multiple roles, use add_role() and document the decision.

The WooCommerce developer docs confirm that order status transitions fire on the action hook named after the destination status, which is why woocommerce_order_status_completed is the correct hook and not woocommerce_payment_complete. The WordPress codex entry for WP_User::set_role() documents every accepted argument.

The hook name tells you exactly when it fires. woocommerce_order_status_completed fires after the status transition, not after payment, and not at checkout submission.

Filtering by Product Category Instead of Product ID

If you sell products in a “Membership” category and want every product in that category to trigger the role change, swap the product ID check for a term check:

  • In the no-code workflow, change the condition to check Order Line Items Product Categories Contains and enter your category slug.
  • In the code snippet, replace in_array( $item->get_product_id(), $target_ids ) with has_term( 'membership', 'product_cat', $item->get_product_id() ).

Category-based triggers are more maintainable at scale. Adding a new product to the category is enough to include it in the automation. There is no list of product IDs to update.

Handling Guest Purchasers

Every guide written about this topic assumes the buyer has a WordPress account. That assumption breaks the moment you allow guest checkout, which WooCommerce enables by default.

When $order->get_customer_id() returns 0, the buyer has no user record. You have three realistic paths:

  • Force account creation at checkout: go to WooCommerce Settings Accounts and Privacy and enable “Allow customers to create an account during checkout” and “When creating an account, automatically generate a username and password.” WooCommerce creates the account before the order is placed, so get_customer_id() always returns a valid ID by the time your hook fires.
  • Create the account on order completion via code: call wc_create_new_customer( $order->get_billing_email() ) inside your hook when $user_id === 0, then link the order to the new user with $order->set_customer_id( $new_user_id ) and $order->save(). This works but requires careful error handling for duplicate email addresses.
  • Send an account creation email: send the guest a transactional email asking them to create an account. Apply the role on the user_register hook by storing the intended role in order meta and retrieving it on registration. This is the most complex path and only worth building if you cannot change the checkout flow.

The simplest fix for most stores is the first option. Requiring an account at checkout adds one step for the buyer but eliminates the guest-to-role problem entirely. The WooCommerce documentation covers order management and customer account settings in detail.

Role Revocation on Refund or Cancellation

This is the scenario almost no tutorial covers, and it is the one that generates the most angry users. A customer gets refunded, keeps their premium role, and continues accessing content they are no longer paying for.

To handle revocation, hook into order status changes that indicate a reversal:

add_action( 'woocommerce_order_status_refunded', 'revoke_role_on_refund', 10, 1 );
add_action( 'woocommerce_order_status_cancelled', 'revoke_role_on_refund', 10, 1 );

function revoke_role_on_refund( $order_id ) {
    $order   = wc_get_order( $order_id );
    $user_id = $order->get_customer_id();

    if ( ! $user_id ) {
        return;
    }

    $target_ids = array( 123, 456 ); // Same product IDs as your assignment hook
    $has_target = false;

    foreach ( $order->get_items() as $item ) {
        if ( in_array( $item->get_product_id(), $target_ids, true ) ) {
            $has_target = true;
            break;
        }
    }

    if ( $has_target ) {
        $user = new WP_User( $user_id );
        $user->set_role( 'customer' ); // Revert to default WooCommerce role
    }
}

One important caveat: if a customer has multiple orders, reverting the role on one refund may remove access they earned through a separate valid purchase. Before calling set_role() in the revocation path, query the user’s other completed orders to confirm no other qualifying purchase exists. That extra check adds complexity, but skipping it causes false revocations.

In the no-code workflow path, you build this as a second workflow with the Order Status Changed trigger, a condition checking for status equal to refunded or cancelled, and the same Change User Role action pointing back to customer. If you are running WooCommerce Subscriptions, the WooCommerce Subscriptions integration documentation covers subscription-specific status triggers including expired, cancelled, and on-hold.

Triggering a Role Change Based on Order Total

The VIP tier use case works by checking order total rather than product ID. In the no-code path, swap the product condition for Order Total Greater Than and enter your threshold. In code, replace the product ID check with:

if ( (float) $order->get_total() >= 200 ) {
    $user = new WP_User( $user_id );
    $user->set_role( 'vip_customer' );
}

The threshold of $200 is illustrative. Set it to whatever your margins support. Note that this fires on every completed order, so a customer who places a large order once will keep the VIP role indefinitely unless you add a revocation rule tied to the role’s business logic, such as no qualifying order in the last 12 months.

What This Approach Does Not Cover

Being clear about the limits saves you a debugging session later:

  • Subscriptions with automatic renewal: WooCommerce Subscriptions creates renewal orders automatically. The role assignment hook fires on renewal if the renewal order reaches completed, which is usually what you want. But the revocation logic needs to hook into subscription-specific events like woocommerce_subscription_status_cancelled, not just order status changes.
  • Role expiry after a fixed period: neither approach here schedules a future revocation. For time-limited access you need a cron job or a scheduled workflow using the schedule trigger in Pro to check and revert roles after a set number of days.
  • Sites with zero traffic during off-hours: code snippet hooks fire in-process and are reliable. Workflow automation that uses delayed actions depends on WordPress Cron, which only fires when someone visits the site. On very low-traffic sites, configure a real server-side cron to keep WP-Cron reliable.
  • Multisite: WP_User::set_role() sets the role on the current site in the network. Cross-site role assignment requires explicitly switching blog context with switch_to_blog().

Choosing Between the Two Approaches

Situation Best approach Why
Single product, no existing code setup No-code workflow Five minutes to configure, execution logs included, no PHP required
Multiple products across multiple conditions No-code workflow with branching Visual canvas makes multi-condition logic readable at a glance
Already managing custom snippets on the site Code snippet No additional plugin, logic lives with your other customisations
Guest checkout is enabled and cannot be changed Code snippet Easier to add account-creation logic inline before role assignment
Need role revocation on subscription cancellation No-code workflow + Subscriptions integration Subscription-specific triggers are not available via a simple order hook
Agency managing 10 or more client sites No-code workflow, exported as JSON Workflow import/export lets you redeploy the same logic across sites in seconds

Troubleshooting: Why Your Role Change Is Not Working

Work through this list in order. The first item on the list accounts for roughly 60% of failed setups, so check it before anything else.

  • Order status is “Processing”, not “Completed”: digital products set to auto-complete will reach completed immediately. Physical products stay at processing until you manually complete them or a fulfilment plugin does it. If your hook is not firing, check the order status in WooCommerce Orders first.
  • Guest checkout returned user ID 0: add a check for $user_id === 0 and log or skip gracefully. Silently failing on guest orders is the most common source of “it works sometimes” reports.
  • The role slug does not exist: set_role() silently does nothing if the role slug is not registered. Confirm the slug with wp_roles()->get_names() in the WP-CLI or in a temporary debug line. Slugs are case-sensitive and use underscores, not hyphens, in most plugins.
  • Another plugin is reverting the role: some membership plugins reset user roles on every page load or login. Check for plugins like MemberPress, Paid Memberships Pro, or Restrict Content Pro that manage roles independently, and decide which system owns the role.
  • The hook fired but the product condition did not match: if you are using the code snippet, add a temporary error_log() call to output the product IDs in the order. Compare them against your $target_ids array. Variable products return a variation ID from get_product_id() on the line item; use get_parent_id() on the variation to get the parent product ID.

Variable product line items return the variation ID, not the parent product ID. That single mismatch causes more silent failures than any other source in this workflow.

What This Setup Pairs Well With

Role assignment on purchase is rarely the only thing you want to happen when an order completes. Common additions to the same workflow include sending a welcome email through the visual email builder, tagging the contact in FluentCRM via the FluentCRM integration, or enrolling the customer in a LearnDash course via the LearnDash integration. All of these can live in the same workflow on the same canvas, triggered by the same Order Completed event.

If you are sending post-purchase review requests alongside the role change, the article on automating WooCommerce review request emails covers that setup. For course access specifically, the guide on giving course access after a WooCommerce purchase goes deeper on the LearnDash side of the configuration.

Cost Comparison: Plugin vs. Code vs. Workflow Automation

Approach Year 1 cost Ongoing cost Execution logs Revocation support
Code snippet only $0 Developer time to maintain Manual (error_log) Manual (second hook)
Dedicated role plugin (e.g. Members Only) $69 to $149/year (estimated) Annual renewal Varies by plugin Yes, most include expiry
Krom Automation free tier $0 $0 Full per-step audit trail Yes, via second workflow
Krom Automation Pro (Basic plan) $69/year (launch price) $69/year or $199 lifetime Full per-step audit trail Yes, plus subscription triggers

The free tier covers the core use case for most stores. Pro adds subscription-specific triggers, scheduled role expiry via the schedule trigger, and the visual email builder for the welcome message that follows the role change.

Other Tools Worth Knowing About

Pollify is wpRigel’s Gutenberg-native poll, survey and quiz plugin. Polls are built as real blocks inside the editor, so there are no shortcodes to paste and no separate admin panel to navigate. It pairs well with membership sites that want to survey members after they gain access.

Commandify is a command palette for the WordPress admin. Press Cmd or Ctrl plus K to jump anywhere, search orders by customer name or email, and run admin actions without clicking through menus. It is the only command palette with real WooCommerce order, product and customer commands built in, which makes managing a high-volume store noticeably faster.

Next Steps

If you are starting fresh, install the free plugin, build the Order Completed workflow, test it with the simulator, and add the cancellation workflow immediately. Do not leave the revocation path for later. If you are already running a code snippet and it is mostly working, the most valuable addition is adding an execution log so you can see which orders triggered a role change and which ones silently failed.

Try Krom Automation free on WordPress.org and build your first role-change workflow in under ten minutes. When you are ready to add subscription triggers, scheduled expiry, and the visual email builder, Pro plans start at $69 a year.

Frequently Asked Questions

Which WooCommerce hook should I use to trigger a role change after an order is completed?

Use woocommerce_order_status_completed. It fires after the order status transitions to “completed”, which is the correct point because payment is confirmed and fulfilment is acknowledged. Do not use woocommerce_payment_complete, which fires at payment capture and may precede the completed status by hours on manual orders.

What is the difference between set_role() and add_role() in WordPress?

set_role() replaces the user’s existing primary role with the new one. add_role() adds the new role on top of any existing roles, resulting in a user with multiple roles.

For most membership setups use set_role(). Use add_role() only if your access control layer explicitly supports and expects stacked roles.

How do I change user role only when a specific product is purchased in WooCommerce?

Loop through the order’s line items and check each item’s product ID against your target list using $item->get_product_id(). For variable products, also check wp_get_post_parent_id( $item->get_product_id() ) to match the parent product. Only call set_role() when a match is found.

Can I change a user role after purchase if the customer checked out as a guest?

Not directly, because guest orders have no associated WordPress user record. The cleanest solution is to require account creation at checkout in WooCommerce’s Accounts and Privacy settings. If you must support guest checkout, you can create a user account programmatically on order completion using wc_create_new_customer(), then assign the role to the new user ID.

How do I revert a user role when a WooCommerce order is refunded or cancelled?

Hook into woocommerce_order_status_refunded and woocommerce_order_status_cancelled. Inside the callback, confirm the order contains the qualifying product, then call $user->set_role( 'customer' ) to revert. Before reverting, query the user’s other completed orders to make sure you are not removing access they earned through a separate valid purchase.