> ## Documentation Index
> Fetch the complete documentation index at: https://dokumentation.websale.de/llms.txt
> Use this file to discover all available pages before exploring further.

# $wsProducts - Product data

> $wsProducts module: dynamically load and display product data in the frontend. Output single products, lists, prices, promotional prices, images and descriptions.

With the `$wsProducts` module, you can dynamically load and display product data in the frontend.

***

## Module overview

**Example / excerpt of** `$wsProducts`

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{= $wsProducts | json }}
```

**JSON output**

```json theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{
  "load": "ƒ()",
  "loadByNumber": "ƒ()",
  "loadByCustomNumber": "ƒ()",
  "variantInfo": "ƒ()"
}
```

**Note:** `ƒ()` denotes a function.

**Methods overview**

| **Method**             | **Return type** | **Description**                                   |
| ---------------------- | --------------- | ------------------------------------------------- |
| `load()`               | map             | Returns a product based on the product ID.        |
| `loadByNumber()`       | map             | Returns a product based on the item number.       |
| `loadByCustomNumber()` | map             | Returns a product based on a user-defined number. |
| `variantInfo()`        | map             | Returns the variant data for a product.           |

***

## Templates

By default, products are displayed via the template `product.htm`. This is located in the `views` directory. The name `product.htm` and the storage location must not be changed, as the template is hard-wired in the software and is not configurable or customizable.

However, product data can be flexibly integrated on other pages as well, for example:

* Home page → display of top sellers, offers, or a product selection.
* Basket page → cross-selling products as purchase recommendations.
* Category pages & search results → custom product lists with filters.
* Checkout & order confirmation → display of complementary products or discount campaigns.

With `$wsProducts`, product information can be retrieved dynamically and integrated individually into various templates to enable targeted presentation of items.

***

## Variables

No variables are available for `$wsProducts`.

***

## Methods

### \$wsProducts.load()

Returns a product based on the product ID.

**Signature**<br />`$wsProducts.load(productId)`

**Return value**<br />`map` - Product map with all product data.

**Parameters**

| **Name**    | **Type** | **Required** | **Description**    |
| ----------- | -------- | ------------ | ------------------ |
| `productId` | string   | yes          | ID of the product. |

**Example** that loads a product and outputs the product name.

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $myProduct = $wsProducts.load("100-12345") }}
Name: {{= $myProduct.name }}
```

### \$wsProducts.loadByNumber()

Returns a product based on the item number.

**Signature**<br />`$wsProducts.loadByNumber(itemNumber)`

**Return value**<br />`map` - Product map with all product data.

**Parameters**

| **Parameter** | **Type** | **Required** | **Description**             |
| ------------- | -------- | ------------ | --------------------------- |
| `itemNumber`  | string   | yes          | Item number of the product. |

**Example** that loads a product by item number:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $myProduct = $wsProducts.loadByNumber("ART-001") }}
Name: {{= $myProduct.name }}
```

### \$wsProducts.loadByCustomNumber()

Returns a product based on a user-defined number, for example EAN or GTIN.

**Signature**<br />`$wsProducts.loadByCustomNumber(customNumber)`

**Return value**<br />`map` - Product map with all product data.

**Parameters**

| **Parameter**  | **Type** | **Required** | **Description**      |
| -------------- | -------- | ------------ | -------------------- |
| `customNumber` | string   | yes          | User-defined number. |

**Example** that loads a product by GTIN.

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $myProduct = $wsProducts.loadByCustomNumber("4006381333931") }}
Name: {{= $myProduct.name }}
```

<Info>
  By using the `$wsProducts.load..()` functions, various variables are available to retrieve and output data about the product. Below is an overview of which variables are available.
</Info>

#### Product data (return value of \$wsProducts.load())

First, it is necessary to assign the map with the product data, as shown in the example above, to a local variable. This can then be used at various places in the template.

**JSON output of the variable**

```json theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{
  "id": "...",
  "active": "...",
  "name": "...",
  "descr": "...",
  "itemNumber": "...",
  "price": "...",
  "rawPrice": "...",
  "promotionInfo": "...",
  "taxRateId": "...",
  "storeId": "...",
  "custom": { ... },
  "base": null,
  "variantSelection": { },
  "getFullPriceInfo": "ƒ()"
}
```

**Variables overview**

| **Variable**         | **Type** | **Description**                                                                                                                                                                                                                 |
| -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                 | string   | Unique product ID assigned by the shop.                                                                                                                                                                                         |
| `active`             | string   | Returns whether the product is active in the shop.   <br />Return values:  <br />- `always` - the product is always active. <br />- `never` - the product is never active. <br />- `test` - the product is active in test mode. |
| `name`               | string   | Name of the product.                                                                                                                                                                                                            |
| `descr`              | string   | Description of the product.                                                                                                                                                                                                     |
| `itemNumber`         | string   | Item number of the product.                                                                                                                                                                                                     |
| `price`              | float    | The currently valid price of the product. If a promotional price is active, this is the promotional price. See [Price of a product](#price-of-a-product).                                                                       |
| `rawPrice`           | float    | The standard price of the product, independent of active promotional prices. Intended for display as a strikethrough price.                                                                                                     |
| `promotionInfo`      | string   | Text of the active promotional price. The field is only present if a promotional price is active and it carries a text.                                                                                                         |
| `taxRateId`          | string   | Tax rate ID of the product.                                                                                                                                                                                                     |
| `storeId`            | string   | Stock item number of the product.                                                                                                                                                                                               |
| `custom`             | map      | Contains all configured free product fields of the product.                                                                                                                                                                     |
| `base`               | map      | Contains the data of the base product if it is a variant.                                                                                                                                                                       |
| `variantSelection`   | map      | Contains the selected variant attributes, for example colour and size.                                                                                                                                                          |
| `getFullPriceInfo()` | ƒ()      | Returns price, standard price and promotion text for any price field. See [Read further price fields](#read-further-price-fields).                                                                                              |

### \$wsProducts.variantInfo()

Returns the variant data for a product.

**Signature**<br />`$wsProducts.variantInfo(productId)`

**Return value**<br />`map` - Map with variant data.

**Parameters**

| **Name**    | **Type** | **Required** | **Description**                                    |
| ----------- | -------- | ------------ | -------------------------------------------------- |
| `productId` | string   | yes          | ID of the product whose variants are to be loaded. |

**Example** that loads the variant info of a product.

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $myVariants = $wsProducts.variantInfo("100-12345") }}
Number of variants: {{= $myVariants.numVariants }}
```

<Info>
  By using the `$wsProducts.variantInfo()` function, various variables are available to retrieve and output data about the product. Below is an overview of which variables are available.
</Info>

#### Variant data (return value of \$wsProducts.variantInfo())

First, it is necessary to assign the map with the variant data, as shown in the example above, to a local variable. This can then be used at various places in the template.

**JSON output of the variable**

```json theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{
  "numVariants": 5,
  "variantAttributes": [
    {
      "name": "Color",
      "options": [
        { "name": "Red" },
        { "name": "Blue" }
      ]
    }
  ],
  "resolve": "ƒ()"
}
```

**Variables overview**

| **Variable**        | **Type** | **Description**                              |
| ------------------- | -------- | -------------------------------------------- |
| `numVariants`       | int      | Number of variants.                          |
| `variantAttributes` | array    | List of variant attributes.                  |
| `[$i].name`         | string   | Name of the attribute, for example "Colour". |
| `[$i].options`      | array    | List of options.                             |
| `[$i].name`         | string   | Name of the option, for example "Red".       |

#### \$wsProducts.variantInfo().resolve()

`resolve()` is a method of the return value of `$wsProducts.variantInfo()`. It takes a possibly incomplete or invalid attribute selection and returns the closest matching existing variant.

**Why** `resolve()` **is needed**<br />Variant products only exist in certain attribute combinations.<br />When a customer changes a single attribute, for example the colour, the previously selected overall combination may no longer be available. <br />In this case, `resolve()` finds the next possible valid variant without you having to search through all available combinations yourself.<br /><br />**Example** <br />A T-shirt is available in these combinations:

| Colour | Size |
| ------ | ---- |
| Red    | S    |
| Blue   | S    |
| Red    | L    |

The customer currently sees "Red, L" and clicks on "Blue". However, the combination "Blue, L" does not exist, so `resolve()` returns "Blue, S" instead, that is the only valid variant in which the colour Blue is preserved.<br />The `fixate` parameter controls which attribute is considered immutable. In this case it is the colour, since the user selected the colour.

**Signature**<br />`$variantInfo.resolve(selection, fixate)`

**Parameters**

| Name        | Type   | Required | Description                                                                                                                                                                                                          |
| ----------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `selection` | map    | yes      | The currently selected attribute values.<br />The map contains attribute names as keys and selected option values as values, for example `{"Colour": "Blue", "Size": "L"}`. <br />Not all attributes need to be set. |
| `fixate`    | string | yes      | Name of the attribute that the user just changed.<br />This value is mandatorily preserved by `resolve()`. <br />All other attributes are adjusted as needed to produce a valid combination.                         |

<br />**Return value**<br />`map` - A product object that corresponds to the resolved variant. Its structure is identical to the return value of [\$wsProducts.load()](/en/frontend/referenz/module/wsproducts#\$wsproducts-load). In case of error, that is when no variant with the fixated attribute value exists, `resolve()` returns `null`.<br /><br />**Example**<br />The user had selected "Red, size L" and clicks on "Blue". Because "Blue, L" does not exist, `resolve()` returns the closest matching variant with the colour Blue.

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $varInfo  = $wsProducts.variantInfo("100-12345") }}
{{ var $resolved = $varInfo.resolve({"Color": "Blue", "Size": "L"}, "Color") }}

{{ if $resolved }}
  <a href="/produkt/{{= $resolved.id }}">Select variant</a>
{{ /if }}
```

***

## Actions

No actions are available for `$wsProducts`.

***

## Examples

In the following examples, the product is assigned to a variable `$myProduct`. This means that all product information can be retrieved and further processed via this variable.

### Name and description of the product

Name and description are standard product data fields that are predefined by the shop system. They belong to the essential fields used to record and display basic product information and are indispensable for order processing.

The technical field names are firmly defined and are addressed in the following form:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
$myProduct.<technical name>
```

The syntax for accessing the product name and the product description is:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<h1>{{= $myProduct.name }}</h1>
<p>{{= $myProduct.descr }}</p>
```

### Weight as an additional product data field

In contrast to standard product data fields, weight is one of the additional product data fields. These offer extended possibilities for recording and displaying product features that go beyond the basic information.

Additional product data fields are:

* Not mandatorily required for order processing, but helpful for product presentation.
* Individually customizable and can be added as required.
* Created via the admin interface or supplied via the product data interface.

The technical names of these fields are addressed in the form:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
$myProduct.custom.<technical name>
```

If a field `weight` was created in the admin area, the weight of a product can be output like this:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<p>Weight: {{= $myProduct.custom.weight }} kg</p>
```

### Product images

The data fields for product images are not standard fields but additional product data fields. To output product images flexibly and in different sizes, these fields must be created as the MultiFormatImage data type. This field type enables storing an image in multiple formats and at the same time provides automatic conversion, so the images are available in the desired sizes. The number of additional product data fields with this type is not limited, so any number of images can be stored for a product.

The configuration of the image sizes is done in the admin interface in the image converter service. There you can define the desired formats needed for various areas of use. Typically, four image sizes are used:

* mini (thumbnail)
* small
* normal and
* large

In the admin interface image converter service, you also define the storage directory for the images on the server. The path to the image is then output directly via the corresponding variable.

Product images are accessed according to the following scheme

* `$myProduct.custom.<technical field name>.<defined format>`

If, for example, the field `image01` was created for the main image of the product, the images can be output in the various sizes as follows:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<img src="{{= $myProduct.custom.image01.mini}}" alt="{{= $myProduct.name }}">
<img src="{{= $myProduct.custom.image01.small}}" alt="{{= $myProduct.name }}">
<img src="{{= $myProduct.custom.image01.normal}}" alt="{{= $myProduct.name }}">
<img src="{{= $myProduct.custom.image01.large}}" alt="{{= $myProduct.name }}">
```

### Variants of a product

Products that are available in different versions such as size, colour, or material are created as variant products. Variants are not standalone items, but subordinate versions of a main product that differ in certain features. So that variants can be correctly managed and displayed in the shop, special product data fields are used, which are referred to as type-specific product data fields.

These fields are specifically designed to support the requirements of variant products, set products, and other complex product structures. They are deeply anchored in the shop software, firmly predefined, and cannot be changed technically. This means that both the designation and the specifications of these fields cannot be adjusted. This concerns, for example, allowed values, field types, and inheritance mechanisms.

The actual product information of a variant, such as name, description, price, or images, is maintained via the standard and additional product data fields defined for the variants. This data can either be set individually per variant. If no specific values are stored, they are automatically inherited from the main product.

The following syntax is used to access variants of a product:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $myProductVariant = $wsProducts.variantInfo($myProduct.id) }}
{{ foreach $myVariant in $myProductVariant.variantAttributes }}
   <p>Variant name: {{= $myVariant.name }}</p>
{{ /foreach }}
```

### Price of a product

Every product has a sales price displayed in the shop. In addition, a product can carry time-controlled promotional prices that only apply within a maintained period. This section first describes the two fields for the regular price and the promotional price, then the display as a strikethrough price, then the prices of set products, and finally access to further price fields.

#### Current price and standard price

The shop resolves the price anew on every page load. You therefore do not have to check yourself whether a promotion is running. Three fields are available for this.

| **Variable**    | **Type** | **Description**                                                                                                                                         |
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `price`         | float    | The currently valid sales price. If a promotional price is active, this is the promotional price, otherwise the standard price.                         |
| `rawPrice`      | float    | The standard price, independent of active promotional prices.                                                                                           |
| `promotionInfo` | string   | The text of the active promotional price, for example "Sommeraktion". The field is only present if a promotional price is active and it carries a text. |

So that the prices are output with the correct currency, the currency formatting is automatically taken from the shop settings in the admin interface. This means that the currency is displayed either as an ISO code (`EUR`) or as a symbol (`€`), depending on the configuration of the shop.

Whether the prices in the shop are treated net (excl. VAT) or gross (incl. VAT) is also a setting that can be configured in the admin interface.

#### Display strikethrough price and promotion notice

A strikethrough price only makes sense if the standard price is actually above the current price. Therefore, check this before output.

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ if $myProduct.rawPrice > $myProduct.price }}
   <span class="text-decoration-line-through">{{= $myProduct.rawPrice | currency }}</span>
{{ /if }}
<span class="fw-bold">{{= $myProduct.price | currency }}</span>

{{ if $myProduct.promotionInfo }}
   <span class="badge bg-danger">{{= $myProduct.promotionInfo }}</span>
{{ /if }}
```

<Note>
  Promotional prices are maintained per price field in the Admin Interface with a from-to period. Via the interface, this is done with the `scheduledPrices` field, described in the [API reference products](/en/schnittstellen/admin-interface-api/api-referenz-produkte#time-controlled-prices-promotional-prices).
</Note>

#### Prices of set products

For set products, the price is calculated from the main product and the sub-products, anew on every page load. The fields `price` and `rawPrice` contain the values of the set. In addition, the following fields are available.

| **Variable**       | **Type** | **Description**                                                                                                                  |
| ------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `setPrice`         | float    | Price of the set, that is the value that is also in `price`.                                                                     |
| `setRawPrice`      | float    | Price of the set without promotional prices, that is the value that is also in `rawPrice`.                                       |
| `setOrgPrice`      | float    | Reference price of the set from the price of the main product and the prices of all sub-products. Serves as strikethrough price. |
| `setDiscount`      | float    | Savings in percent, that is "You save X %".                                                                                      |
| `setDiscountPrice` | float    | Savings as an amount, that is "You save X euros".                                                                                |

The syntax for access is:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ if $myProduct.isSetProduct }}
   <p>Price: {{= $myProduct.setPrice | currency }}</p>
   <p>Original price: {{= $myProduct.setOrgPrice | currency }}</p>
   <p>Discount in percent: {{= $myProduct.setDiscount }} %</p>
   <p>Discount in numbers: {{= $myProduct.setDiscountPrice | currency }}</p>
{{ /if }}
```

<Note>
  The five set fields are only available for set products. They are not present for all other products. Whether a product is a set is indicated by the `isSetProduct` field, which is present on every product.
</Note>

#### Price in the basket

The price of a basket line item is fixed when it is added. Affected are `$wsBasket.items[].price` and the derived values `total`, `totalNet`, `totalGross` and `totalTax`. If a promotion ends while the item is in the basket, the line item retains the price at the time of adding.

The same applies to the sub-products of a set and to automatically added line items. The product object inside a basket line item, that is `$wsBasket.items[].product`, resolves its price anew on every call. Both values can therefore diverge.

#### Changes to existing templates

<Warning>
  The following changes can affect existing templates. Check your price outputs before updating.

  | Field                                                        | Change                                                                                                                                                                               |
  | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
  | `discountPrice`                                              | Removed from the product and variant object. The identically named key on a basket line item, that is `$wsBasket.items[].discountPrice`, remains unchanged.                          |
  | `totalPrice`                                                 | Removed from the product and variant object.                                                                                                                                         |
  | `setPrice`, `setOrgPrice`, `setDiscount`, `setDiscountPrice` | Previously present on every product, on normal items with the value 0.00. Now only present on set products. Outputs without a check for `isSetProduct` remain empty on normal items. |
  | `setOrgPrice`                                                | Now additionally contains the price of the main product. Previously the value only summed the sub-products. Strikethrough prices based on this field therefore become higher.        |
  | `setDiscount`, `setDiscountPrice`                            | Now also deliver values other than 0 where previously 0.00 was mandatory.                                                                                                            |
  | `price`                                                      | Now contains the resolved price; for set products the total sum of the set. This applies to all fields of type Price, including additional fields under `custom.`.                   |
</Warning>

#### Read further price fields

In addition to the standard price, a shop can maintain further price fields as additional product data fields. These fields can also carry promotional prices, and the direct access via `$myProduct.custom.<field>` already returns the resolved price. You only need `getFullPriceInfo()` when you additionally need the standard price or the promotion text of such a field.

**Signature**<br />`$myProduct.getFullPriceInfo(field)`

**Return value**<br />`map` - Map with the fields `price`, `rawPrice` and `promotionInfo`. For an unknown field, `null` is returned.

**Parameters**

| **Name** | **Type** | **Required** | **Description**                                                                                                                                                                                         |
| -------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `field`  | string   | yes          | Technical name of the price field. Standard product data fields are addressed directly, for example `price`. Additional product data fields carry the prefix `custom.`, for example `custom.listPrice`. |

**Example** that outputs an additional price field with strikethrough price.

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $cPriceInfo = $myProduct.getFullPriceInfo("custom.listPrice") }}

{{ if $cPriceInfo }}
   {{ if $cPriceInfo.rawPrice > $cPriceInfo.price }}
      <span class="text-decoration-line-through">{{= $cPriceInfo.rawPrice | currency }}</span>
   {{ /if }}
   <span>{{= $cPriceInfo.price | currency }}</span>
{{ /if }}
```

#### Where these fields are available

The price fields belong to the product object itself. They are therefore available everywhere a product object is delivered, and not only for `$wsProducts.load()`.

Via module methods:

* `$wsProducts.load()`, `$wsProducts.loadByNumber()`, `$wsProducts.loadByCustomNumber()` and `$wsProducts.loadNext()`
* `$wsProducts.variantInfo(id).resolve(selection, fixate)`
* `$wsProduct.load()` and `$wsProduct.variantInfo(id).resolve(selection, fixate)`
* `$wsCategories.loadProducts(categoryId)`
* `$wsSearch.search(params, id).products`
* `$wsWatchList.loadWatchList(watchListId).items[].product`
* `$wsLastSeenProducts.load()`

Via page variables and nested objects:

* `$wsViews.current.info.product` and `$wsViews.current.info.products[]`
* `$wsViews.current.info.basketItem.product` and `$wsViews.current.info.product`
* `$wsBasket.items[].product`
* `$wsNavigation.path[].object`, when `type` has the value `product`
* `$product.base`, that is the base product of a variant

### Availability of a product

In addition to all other product information, the availability of a product is one of the most important pieces of information for the buyer. Each product can be given a stock level that determines whether and in what quantity an item can be ordered. Additionally, the current stock and the corresponding delivery status can be displayed in the shop.

Access to the stock is not made directly via `$wsProducts`, but via the separate module `$wsInventory`.

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $myProductInventoryInfo = $wsInventory.load($myProduct.id) }}
{{ if $myProductInventoryInfo.active }}
      Current quantity: {{= $myProductInventoryInfo.amount }}
{{ /if }}
```

You can find detailed information about data access of the stock module [here](/en/frontend/referenz/module/wsinventory).

You can find more practical examples for implementing products and product variants here:

→ [Practical examples: products](/en/frontend/praxisbeispiele/produkte/anzeige-von-produkten)

***

## Related links

* [actions - Products](/en/konfiguration/actions-fehlertexte-e-mails/actions-produkte)
* [API reference products](/en/schnittstellen/admin-interface-api/api-referenz-produkte)
* [\$wsInventory - Stock & availability](/en/frontend/referenz/module/wsinventory)


## Related topics

- [$wsProductRating - Product ratings](/en/frontend/referenz/module/wsproductrating.md)
- [$wsLastSeenProducts - Recently viewed products](/en/frontend/referenz/module/wslastseenproducts.md)
- [$wsExternalData - External data](/en/frontend/referenz/module/wsexternaldata.md)
- [API reference products](/en/schnittstellen/admin-interface-api/api-referenz-produkte.md)
- [$wsWatchList - Watchlists](/en/frontend/referenz/module/wswatchlist.md)
