Add a WooCommerce product to cart programmatically with PHP

There are many legitimate reasons to add a product to a WooCommerce cart programmatically rather than through the standard Add-to-Cart button: promotional landing pages that pre-load a cart with a specific product and redirect straight to checkout, one-click upsell flows, order-bump plugins, referral links with a pre-configured product, or custom bundles assembled via a configurator. WooCommerce provides the WC()->cart->add_to_cart() method for this purpose, which accepts the product ID, quantity, and variation ID (for variable products). The method returns the cart item key on success or false on failure (out-of-stock, purchase limits exceeded, etc.). You can also clear the cart before adding the new product with WC()->cart->empty_cart() and then redirect to checkout with wc_get_checkout_url() to create a frictionless single-product checkout flow. This is the same pattern that many “buy now” button plugins use internally. For the URL-based approach (a link that adds a product when clicked), WordPress rewrites handle the product slug and WooCommerce’s own ?add-to-cart=ID query string works without any code. The examples below show the PHP API approach, suitable for hooks and custom actions. See also the checkout field guide and the thank you page guide for related customisation patterns.

Problem: You need to add a specific WooCommerce product to a customer’s cart automatically via PHP — on a landing page visit, after a form submission, or as part of a promotional flow.

Solution: Add the following code to your functions.php file or a custom plugin:

/**
 * Add a simple product to the cart programmatically.
 *
 * @param int $product_id
 * @param int $quantity
 * @return string|false Cart item key on success, false on failure.
 */
function helloadmin_add_product_to_cart( int $product_id, int $quantity = 1 ) {
    if ( ! function_exists( 'WC' ) || ! WC()->cart ) {
        return false;
    }
    return WC()->cart->add_to_cart( $product_id, $quantity );
}

/**
 * Add a variable product to the cart.
 * $variation_id = the specific product variation (e.g. size: Large)
 * $variation     = the variation attributes array
 */
function helloadmin_add_variation_to_cart( int $product_id, int $variation_id, array $variation, int $quantity = 1 ) {
    if ( ! function_exists( 'WC' ) || ! WC()->cart ) {
        return false;
    }
    return WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation );
}

/**
 * Landing page example: add product 42 and redirect to checkout.
 * Attach to a custom URL handler or page template.
 */
add_action( 'template_redirect', 'helloadmin_landing_page_add_to_cart' );
function helloadmin_landing_page_add_to_cart() {
    if ( ! is_page( 'promo-landing' ) ) {
        return;
    }
    if ( ! function_exists( 'WC' ) ) {
        return;
    }
    WC()->cart->empty_cart(); // optional: clear existing cart
    $cart_key = WC()->cart->add_to_cart( 42, 1 ); // product ID 42
    if ( $cart_key ) {
        wp_redirect( wc_get_checkout_url() );
        exit;
    }
}

/**
 * Add a free gift product when cart total exceeds $100.
 */
add_action( 'woocommerce_before_calculate_totals', 'helloadmin_add_free_gift', 10, 1 );
function helloadmin_add_free_gift( $cart ) {
    if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
        return;
    }
    $gift_id   = 99;  // ID of the free gift product
    $threshold = 100; // cart subtotal threshold
    $subtotal  = $cart->get_subtotal();
    $has_gift  = false;

    foreach ( $cart->get_cart() as $item ) {
        if ( (int) $item['product_id'] === $gift_id ) {
            $has_gift = true;
            if ( $subtotal < $threshold ) {
                $cart->remove_cart_item( $item['key'] ); // remove if below threshold
            }
            break;
        }
    }
    if ( ! $has_gift && $subtotal >= $threshold ) {
        WC()->cart->add_to_cart( $gift_id, 1 );
    }
}

NOTE: The woocommerce_before_calculate_totals hook fires on every cart recalculation, which can happen multiple times per page load. The is_admin() && ! defined( ‘DOING_AJAX’ ) guard prevents the hook from running during backend order creation. For the free-gift pattern, also handle the case where the customer manually removes the gift item — you may want to allow removal but disable the add-to-cart button for that product using the woocommerce_is_purchasable filter.