Product
Display WooCommerce product data in your templates. Requires WooCommerce to be active.
The Product provider inherits all Post fields — title, link, excerpt, thumbnail, dates, author, custom fields, and everything else. Only product-specific fields are listed here.
The Product provider only activates when WooCommerce is active. If WooCommerce is deactivated, product fields return empty values. Post fields (title, link, etc.) still work because products are a custom post type.
How It Works
When you loop over products with post_type: 'product', use product as your variable name. The CPT routing picks it up automatically and makes all product fields available:
{% for product in get_posts({post_type: 'product', posts_per_page: 6}) %}
{{ product.title }}
{{ product.price | currency }}
{% endfor %}
You still have access to every Post field — product.title, product.thumbnail, product.excerpt, product.link, etc.
Product-Specific Fields
Pricing
| Field | Returns | Description |
|---|---|---|
price | string | Effective price (sale price if on sale, regular otherwise) |
regular_price | string | Regular price |
sale_price | string | Sale price (empty if not on sale) |
is_on_sale | bool | Whether the product is currently on sale |
discount_percent | float | Discount percentage |
price_html | string | Full WooCommerce price HTML (includes <del> for sale) |
price_suffix | string | Price suffix text (e.g. "excl. tax") |
{{ product.price | currency }}
{{ product.regular_price | currency }}
{{ product.sale_price | currency }}
{{ product.discount_percent }}%
{{ product.price_html }}
The currency Filter
Formats a number as WooCommerce currency using wc_price(). Uses your store's configured currency symbol, thousand separator, and decimal settings.
{{ product.price | currency }} <!-- $29.99 -->
{{ product.regular_price | currency }} <!-- $39.99 -->
Without currency, you get the raw number. With it, you get the formatted output matching your WooCommerce settings.
Inventory
| Field | Returns | Description |
|---|---|---|
sku | string | Product SKU |
stock_quantity | int | Stock quantity (null if not tracked) |
stock_status | string | instock, outofstock, or onbackorder |
is_in_stock | bool | Whether the product is in stock |
{{ product.sku }}
{{ product.stock_quantity }}
{{ product.stock_status }}
{% if product.is_in_stock %}Available{% endif %}
Product Properties
| Field | Returns | Description |
|---|---|---|
is_virtual | bool | Whether virtual (no shipping) |
is_downloadable | bool | Whether downloadable |
is_featured | bool | Whether marked as featured |
product_type | string | simple, variable, grouped, or external |
weight | string | Product weight |
length | string | Product length |
width | string | Product width |
height | string | Product height |
{{ product.product_type }}
{{ product.weight }}
{{ product.length }} × {{ product.width }} × {{ product.height }}
{% if product.is_featured %}★{% endif %}
Gallery
| Field | Arguments | Returns | Description |
|---|---|---|---|
product_gallery | limit | Image[] | Gallery images |
{% for image in product.product_gallery %}
{{ image.src('medium') }}
{{ image.alt }}
{% endfor %}
{% for image in product.product_gallery(4) %}
<!-- Limited to 4 images -->
{% endfor %}
Each image is a full Image object — src, alt, width, height, and all size variants.
Related Products
| Field | Arguments | Returns | Description |
|---|---|---|---|
upsells | limit | Product[] | Upsell products |
cross_sells | limit | Cross-sell products |
{% for upsell in product.upsells %}
{{ upsell.title }}
{{ upsell.price | currency }}
{% endfor %}
{% for upsell in product.upsells(3) %}
<!-- Limited to 3 upsells -->
{% endfor %}
{% for cross in product.cross_sells(4) %}
{{ cross.title }}
{% endfor %}
Each related product is itself a full Product object — you can access all product fields on it.
Ratings & Reviews
| Field | Returns | Description |
|---|---|---|
average_rating | float | Average rating (0–5) |
review_count | int | Number of reviews |
rating_count | int | Number of ratings |
{{ product.average_rating }} / 5
{{ product.review_count }} reviews
Cart
| Field | Returns | Description |
|---|---|---|
add_to_cart_url | string | Add to cart URL |
add_to_cart_text | string | Add to cart button text |
{{ product.add_to_cart_url }}
{{ product.add_to_cart_text }}
Common Patterns
Product card with sale badge
{% for product in get_posts({post_type: 'product', posts_per_page: 8}) %}
{{ product.thumbnail.src('medium') }}
{{ product.title }}
{% if product.is_on_sale %}
{{ product.regular_price | currency }}
{{ product.price | currency }}
-{{ product.discount_percent }}%
{% else %}
{{ product.price | currency }}
{% endif %}
{{ product.add_to_cart_text }}
{% endfor %}
Product grid with stock status
{% for product in get_posts({post_type: 'product', posts_per_page: 12}) %}
{{ product.thumbnail.src('woocommerce_thumbnail') }}
{{ product.title }}
{{ product.price | currency }}
{% if product.is_in_stock %}
{% if product.stock_quantity and product.stock_quantity <= 3 %}
Only {{ product.stock_quantity }} left
{% else %}
In stock
{% endif %}
{% else %}
Out of stock
{% endif %}
{% endfor %}
Product gallery
<!-- Main image -->
{{ product.thumbnail.src('large') }}
<!-- Gallery thumbnails -->
{% for image in product.product_gallery %}
{{ image.src('thumbnail') }}
{{ image.alt }}
{% endfor %}
Upsells section
{% if product.upsells %}
{% for upsell in product.upsells(4) %}
{{ upsell.thumbnail.src('medium') }}
{{ upsell.title }}
{{ upsell.price | currency }}
{{ upsell.average_rating }} / 5
{% endfor %}
{% endif %}
Don't forget the currency filter when displaying prices. Without it, product.price returns a raw number like 29.99 — no currency symbol, no formatting. Always use {{ product.price | currency }} to match your WooCommerce store settings.
Next Steps
- Post provider — All inherited fields available on products
- Image provider — Working with gallery images and thumbnails
- Loop — Query and loop over products with
get_posts