WooCommerce wc_get_product(): Read Product Data, Pricing, Stock, and Variations with WC_Product

WooCommerce stores product data across three database tables — wp_posts (the product post), wp_postmeta (all product attributes), and wp_term_relationships (categories, tags, and attributes). Reading product data by accessing these tables directly with get_post_meta() works, but it bypasses WooCommerce’s data abstraction layer, misses the in-memory object cache, and silently breaks when WooCommerce migrates data to custom tables (as in WooCommerce 8+ HPOS). The correct API is wc_get_product(), which returns a WC_Product object (or a subclass like WC_Product_Variable) with typed getter methods for every piece of product data. Inside the WooCommerce product loop, $product is already set to the current product — wc_get_product() is needed outside the loop or when accessing a specific product by ID.

Problem: A plugin needs to read a product's price, SKU, stock status, categories, and whether it is on sale — from outside the WooCommerce loop, given a product post ID from a custom query.

Solution: Call wc_get_product( $post_id ) to get a WC_Product object, then use its getter methods. Check instanceof WC_Product_Variable for variable products that need variation-level data.

<?php
$post_id = 123;
$product = wc_get_product( $post_id ); // returns WC_Product or false

if ( ! $product instanceof \WC_Product ) {
    return; // not a valid product
}

// ── Basic product data ─────────────────────────────────────────────────
echo esc_html( $product->get_name() );        // product title
echo esc_html( $product->get_sku() );         // SKU
echo esc_html( $product->get_type() );        // 'simple', 'variable', 'grouped', 'external'
echo $product->get_id();                      // post ID
echo esc_html( $product->get_description() ); // full description
echo esc_html( $product->get_short_description() );

// ── Pricing ────────────────────────────────────────────────────────────
echo wc_price( $product->get_price() );           // formatted with currency symbol
echo esc_html( $product->get_regular_price() );   // raw string e.g. "29.99"
echo esc_html( $product->get_sale_price() );      // empty string if not on sale
$product->is_on_sale();                           // bool
$product->is_purchasable();                       // bool (in stock + purchasable)

// ── Stock / inventory ──────────────────────────────────────────────────
$product->is_in_stock();                          // bool
$product->get_stock_status();                     // 'instock', 'outofstock', 'onbackorder'
$product->get_stock_quantity();                   // int or null if not tracked
$product->managing_stock();                       // bool

// ── Images ────────────────────────────────────────────────────────────
$image_id      = $product->get_image_id();
$gallery_ids   = $product->get_gallery_image_ids(); // array of attachment IDs
echo wp_get_attachment_image( $image_id, 'woocommerce_thumbnail' );

// ── Categories and tags ────────────────────────────────────────────────
$cat_ids  = $product->get_category_ids();  // array of term IDs
$tag_ids  = $product->get_tag_ids();

// ── Variable product: get all variations ──────────────────────────────
if ( $product instanceof \WC_Product_Variable ) {
    $variation_ids = $product->get_children(); // array of variation post IDs

    foreach ( $variation_ids as $vid ) {
        $variation = wc_get_product( $vid );
        if ( $variation instanceof \WC_Product_Variation ) {
            echo esc_html( $variation->get_sku() );
            echo wc_price( $variation->get_price() );
            print_r( $variation->get_attributes() ); // [ 'pa_color' => 'red', ... ]
        }
    }

    // Get cheapest and most expensive variation price
    echo wc_price( $product->get_variation_price( 'min' ) );
    echo wc_price( $product->get_variation_price( 'max' ) );
}

// ── Programmatically update product data ──────────────────────────────
$product->set_regular_price( '39.99' );
$product->set_sale_price( '29.99' );
$product->set_stock_status( 'instock' );
$product->set_manage_stock( true );
$product->set_stock_quantity( 50 );
$product->save(); // persist all changes to the database

NOTE: Never update WooCommerce product pricing by calling update_post_meta( $id, '_price', '29.99' ) directly — WooCommerce maintains three separate price fields (_price, _regular_price, _sale_price) and the _price field is a computed value that the product object updates automatically based on sale status and schedule. Bypassing the object's setters leaves _price out of sync with the sale price, breaking sorting and filtering by price. Always use $product->set_regular_price(), $product->set_sale_price(), and $product->save().