Created
February 24, 2026 19:35
-
-
Save vapvarun/b954bce89fbd181cd21e93092d6d7163 to your computer and use it in GitHub Desktop.
WooCommerce Upsell and Cross-Sell: Complete Guide (woosellservices.com)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Programmatically set upsell products for a WooCommerce product. | |
| * | |
| * WooCommerce stores upsell product IDs in post meta. This | |
| * function demonstrates how to set upsells via code, which | |
| * is useful when migrating data, syncing from an ERP, or | |
| * building automated product recommendation logic. | |
| * | |
| * @param int $product_id The product to add upsells to. | |
| * @param array $upsell_ids Array of product IDs to upsell. | |
| */ | |
| function woosell_set_upsell_products( $product_id, $upsell_ids ) { | |
| // Load the WooCommerce product object | |
| $product = wc_get_product( $product_id ); | |
| if ( ! $product ) { | |
| return new WP_Error( 'invalid_product', 'Product not found: ' . $product_id ); | |
| } | |
| // Validate that all upsell IDs are real published products | |
| $valid_ids = array(); | |
| foreach ( $upsell_ids as $id ) { | |
| $upsell_product = wc_get_product( $id ); | |
| if ( $upsell_product && 'publish' === $upsell_product->get_status() ) { | |
| $valid_ids[] = $id; | |
| } | |
| } | |
| // Set the upsell IDs and save the product | |
| // WooCommerce displays these on the single product page | |
| $product->set_upsell_ids( $valid_ids ); | |
| $product->save(); | |
| return count( $valid_ids ) . ' upsell products set for ' . $product->get_name(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Customize WooCommerce cross-sell display on the cart page. | |
| * | |
| * By default, WooCommerce shows cross-sells below the cart | |
| * table. This function controls how many cross-sells appear | |
| * and adds custom ordering logic to prioritize products with | |
| * the highest conversion potential (based on sales data). | |
| * | |
| * @param array $cross_sells Array of cross-sell product IDs from cart items. | |
| * @return array Filtered and prioritized cross-sell product IDs. | |
| */ | |
| function woosell_prioritize_cross_sells( $cross_sells ) { | |
| if ( empty( $cross_sells ) ) { | |
| return $cross_sells; | |
| } | |
| // Score each cross-sell product by total sales count | |
| // Products that sell well are more likely to convert | |
| $scored = array(); | |
| foreach ( $cross_sells as $product_id ) { | |
| $product = wc_get_product( $product_id ); | |
| if ( $product ) { | |
| $scored[ $product_id ] = $product->get_total_sales(); | |
| } | |
| } | |
| // Sort by sales descending so best sellers appear first | |
| arsort( $scored ); | |
| // Return only top 4 cross-sell products to avoid clutter | |
| return array_slice( array_keys( $scored ), 0, 4 ); | |
| } | |
| add_filter( 'woocommerce_cart_crosssell_ids', 'woosell_prioritize_cross_sells' ); | |
| /** | |
| * Change the number of cross-sell columns displayed on cart page. | |
| * Default is 2 columns; 4 columns works better for visual products. | |
| */ | |
| add_filter( 'woocommerce_cross_sells_columns', function() { | |
| return 4; | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Add a one-click order bump above the Place Order button. | |
| * | |
| * Displays a highlighted product offer that customers can | |
| * add to their cart with a single checkbox click during | |
| * checkout. This technique typically converts at 5-15% | |
| * and significantly increases average order value. | |
| * | |
| * @since 1.0.0 | |
| */ | |
| // Define the bump product ID and discount percentage | |
| define( 'WOOSELL_BUMP_PRODUCT_ID', 123 ); | |
| define( 'WOOSELL_BUMP_DISCOUNT', 30 ); | |
| /** | |
| * Display the order bump offer box at checkout. | |
| * Hooked just before the payment section for maximum visibility. | |
| */ | |
| function woosell_display_order_bump() { | |
| $product = wc_get_product( WOOSELL_BUMP_PRODUCT_ID ); | |
| if ( ! $product || ! $product->is_purchasable() ) { | |
| return; | |
| } | |
| // Don't show if product is already in cart | |
| foreach ( WC()->cart->get_cart() as $cart_item ) { | |
| if ( $cart_item['product_id'] === WOOSELL_BUMP_PRODUCT_ID ) { | |
| return; | |
| } | |
| } | |
| $regular_price = $product->get_regular_price(); | |
| $bump_price = $regular_price * ( 1 - WOOSELL_BUMP_DISCOUNT / 100 ); | |
| // Render the order bump HTML with inline styles | |
| printf( | |
| '<div id="woosell-order-bump" style="border:2px dashed #f0c14b;background:#fffbf0;padding:20px;margin:20px 0;border-radius:8px;"> | |
| <label style="display:flex;align-items:center;gap:12px;cursor:pointer;font-size:16px;"> | |
| <input type="checkbox" name="woosell_add_bump" value="1" style="width:20px;height:20px;" /> | |
| <span> | |
| <strong>One-Time Offer:</strong> Add %s for just %s | |
| <span style="text-decoration:line-through;color:#999;">%s</span> | |
| <span style="color:#e63946;font-weight:bold;">(%d%% OFF)</span> | |
| </span> | |
| </label> | |
| <p style="margin:8px 0 0 32px;color:#666;font-size:14px;">%s</p> | |
| </div>', | |
| esc_html( $product->get_name() ), | |
| wc_price( $bump_price ), | |
| wc_price( $regular_price ), | |
| WOOSELL_BUMP_DISCOUNT, | |
| esc_html( $product->get_short_description() ) | |
| ); | |
| } | |
| add_action( 'woocommerce_review_order_before_payment', 'woosell_display_order_bump' ); | |
| /** | |
| * Process the order bump: add product to cart if checkbox was checked. | |
| * Applies the discount as a custom fee so it shows in order totals. | |
| */ | |
| function woosell_process_order_bump() { | |
| if ( ! isset( $_POST['woosell_add_bump'] ) ) { | |
| return; | |
| } | |
| $product = wc_get_product( WOOSELL_BUMP_PRODUCT_ID ); | |
| if ( ! $product ) { | |
| return; | |
| } | |
| // Add to cart and apply discount as negative fee | |
| WC()->cart->add_to_cart( WOOSELL_BUMP_PRODUCT_ID ); | |
| $discount = $product->get_regular_price() * ( WOOSELL_BUMP_DISCOUNT / 100 ); | |
| WC()->cart->add_fee( | |
| sprintf( '%s - %d%% checkout discount', $product->get_name(), WOOSELL_BUMP_DISCOUNT ), | |
| -$discount | |
| ); | |
| } | |
| add_action( 'woocommerce_checkout_update_order_review', 'woosell_process_order_bump' ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Display a post-purchase upsell offer on the thank-you page. | |
| * | |
| * After completing a purchase, customers are in a buying | |
| * mindset. Showing a relevant product offer on the order | |
| * confirmation page converts at 3-8% according to industry | |
| * benchmarks. This adds the product directly to a new cart | |
| * for one-click purchase. | |
| * | |
| * @param int $order_id The completed order ID. | |
| */ | |
| function woosell_thankyou_upsell( $order_id ) { | |
| $order = wc_get_order( $order_id ); | |
| if ( ! $order ) { | |
| return; | |
| } | |
| // Find a relevant upsell based on what was just purchased | |
| $upsell_id = null; | |
| foreach ( $order->get_items() as $item ) { | |
| $product = $item->get_product(); | |
| $upsell_ids = $product->get_upsell_ids(); | |
| if ( ! empty( $upsell_ids ) ) { | |
| // Pick the first upsell that wasn't in this order | |
| $ordered_ids = wp_list_pluck( $order->get_items(), 'product_id' ); | |
| $available = array_diff( $upsell_ids, $ordered_ids ); | |
| if ( ! empty( $available ) ) { | |
| $upsell_id = reset( $available ); | |
| break; | |
| } | |
| } | |
| } | |
| if ( ! $upsell_id ) { | |
| return; | |
| } | |
| $upsell = wc_get_product( $upsell_id ); | |
| if ( ! $upsell || ! $upsell->is_purchasable() ) { | |
| return; | |
| } | |
| // Build the add-to-cart URL for one-click purchase | |
| $add_url = add_query_arg( | |
| array( | |
| 'add-to-cart' => $upsell_id, | |
| 'quantity' => 1, | |
| ), | |
| wc_get_checkout_url() | |
| ); | |
| // Display the upsell offer | |
| printf( | |
| '<div style="background:#f8f9fa;border:1px solid #dee2e6;border-radius:12px;padding:24px;margin:24px 0;text-align:center;"> | |
| <h3 style="margin-top:0;">Complete Your Purchase</h3> | |
| <p>Customers who bought %s also love:</p> | |
| <div style="margin:16px 0;">%s</div> | |
| <h4>%s</h4> | |
| <p style="font-size:24px;font-weight:bold;color:#2d3436;">%s</p> | |
| <a href="%s" style="display:inline-block;background:#0073aa;color:#fff;padding:12px 32px;border-radius:6px;text-decoration:none;font-weight:bold;">Add to Cart & Checkout</a> | |
| </div>', | |
| esc_html( $order->get_items() ? current( $order->get_items() )->get_name() : '' ), | |
| $upsell->get_image( 'woocommerce_thumbnail' ), | |
| esc_html( $upsell->get_name() ), | |
| $upsell->get_price_html(), | |
| esc_url( $add_url ) | |
| ); | |
| } | |
| add_action( 'woocommerce_thankyou', 'woosell_thankyou_upsell', 5 ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment