# Product

> Product entity - components, queries, links, and related actions

Your storefront encounters the **Product** entity when fetching catalog data: category listings, search results, and product detail pages. A Product holds general information about a buyable good or service. It is not buyable by itself; instead, each Product links to one or more [ProductVariant](/frontend/api-reference/entities/product-variant) entities that represent the actual purchasable SKUs.

**Entity: Product**

Components: base, description, info, media, prices, rating, seo, specifications, flags, defaultVariant, analytics, brand, optionGroups

Links:

- ecommerce/product/all-categories → Category (multi)
- ecommerce/product/breadcrumb → BreadcrumbItem (multi)
- ecommerce/product/reviews → Review (multi)
- ecommerce/product/variants → ProductVariant (multi)

## Components

### Base

Product name and URL slug.

### ProductBase `base`

```ts
import { ProductBase } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"description":"Name of the base-product. Variants may also have names for further description.","type":"string"},"slug":{"description":"Slug for usage in URLs.","type":"string"},"customFields":{"allOf":[{"$ref":"#/definitions/CustomFields"}]}},"required":["name","slug"],"additionalProperties":false,"id":"entity:Product:base","definitions":{"CustomFields":{"description":"Custom fields are a way for implementations to pass arbitrary data to actions, queries, etc.","title":"CustomFields","id":"CustomFields","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}}
```

### Description

Full HTML-formatted product description.

### ProductDescription `description`

```ts
import { ProductDescription } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","description":"A fragment of HTML. This is a string containing HTML code. Refer to the [HTML spec](https://html.spec.whatwg.org/#elements-3) for a complete list of HTML elements. Example value: `\"<p>Grey cotton knit sweater.</p>\"` Warning: this value will be used as is, so you need to make sure it is safe to render.","title":"HtmlFragment","id":"HtmlFragment","type":"object","properties":{"html":{"type":"string"}},"required":["html"],"additionalProperties":false}
```

### Specifications

Ordered list of technical specification rows (schema.org ). Each row pairs a canonical with a locale-resolved display and a typed ( ) the frontend formats per locale, plus optional per-row section grouping. Rendered by . Detail-only — resolve on the product detail page, not in listings.

### ProductSpecifications `specifications`

```ts
import { ProductSpecifications } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"properties":{"description":"Ordered specification rows. Absent ⇒ no specifications table is rendered.","type":"array","items":{"type":"object","properties":{"wellKnownName":{"type":"string"},"name":{"description":"Locale-resolved display name (schema.org `PropertyValue.name`), e.g. \"Material\". Shown as the row label today, and the fallback when `wellKnownName` is unmapped.","type":"string"},"value":{"description":"Typed value; the frontend formats it per locale. Bare primitives cover text / numeric / boolean specs; `Measurement` carries a quantity + its unit, `Money` a price. Ambiguous types (ISO date / duration strings) get a single-key wrapper if added later.","anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"$ref":"#/definitions/Measurement"},{"$ref":"#/definitions/Money"}]},"wellKnownSectionName":{"type":"string"},"sectionName":{"description":"Locale-resolved section heading (Google Merchant `section_name`). Groups rows in the rendered table; the fallback when `wellKnownSectionName` is unmapped.","type":"string"}},"required":["name","value"],"additionalProperties":false}}},"additionalProperties":false,"id":"entity:Product:specifications","definitions":{"Measurement":{"description":"A value-object representing a measurement of a physical quantity. E.g. length, weight, etc.","title":"Measurement","id":"Measurement","type":"object","properties":{"unit":{"type":"string"},"value":{"type":"number"}},"required":["unit","value"],"additionalProperties":false},"Money":{"description":"A Money object describes a monetary value. You can use e.g. `@screeny05/ts-money` to create and manipulate Money objects more elegantly.","title":"Money","id":"Money","type":"object","properties":{"amount":{"description":"Amount in smallest unit of currency. E.g. cents, pence, etc.","type":"number"},"currency":{"description":"ISO 4217 currency code. E.g. 'USD', 'EUR', 'GBP', etc.","type":"string"}},"required":["amount","currency"],"additionalProperties":false}}}
```

#### The well-known vocabulary

`wellKnownName` and `wellKnownSectionName` are what make a row portable between connectors: the frontend maps a well-known name to a localized label, so a connector can emit its own upstream naming in `name` and still get a translated heading. Both fields are optional, and both accept a string outside the union, which passes through unmapped rather than failing.

**WellKnownPropertyName** identifies the row:

`material`, `weight`, `dimensions`, `width`, `height`, `depth`, `length`, `circumference`, `diameter`, `volume`, `area`, `surface`, `warranty`, `countryOfOrigin`, `careInstructions`, `color`, `capacity`

**WellKnownSectionName** groups rows under a heading:

`general`, `dimensions`, `materials`, `care`, `technical`, `warranty`, `connectivity`

### Flags

Marketing labels like "New", "Sale", or "Bestseller".

### ProductFlags `flags`

```ts
import { ProductFlags } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"array","items":{"type":"object","properties":{"variant":{"description":"Style of the flag.","type":"string"},"label":{"description":"Text of the flag.","type":"string"}},"required":["variant","label"],"additionalProperties":false},"id":"entity:Product:flags"}
```

### Info

Cover image, short description, and brand reference. Used on product tiles and listing pages.

### ProductInfo `info`

```ts
import { ProductInfo } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"cover":{"$ref":"#/definitions/Media"},"shortDescription":{"description":"Short product description. This may be used as subtitle on product-tiles. Not to be confused with the description-component which is the full html-description of a product.","type":"string"},"brand":{"description":"Brand of the product. Can also be used for manufacturer or vendor name.","type":"string"},"brandLink":{"allOf":[{"$ref":"#/definitions/Link"}]}},"required":["cover"],"additionalProperties":false,"id":"entity:Product:info","definitions":{"Media":{"description":"A Media object describes a media asset to be displayed or played in the browser. Currently supported are images, videos, and audio. Future support for 3D, 360° images, and other media types is planned. You can use `type` for type-narrowing.","title":"Media","id":"Media","anyOf":[{"$ref":"#/definitions/MediaImage"},{"$ref":"#/definitions/MediaVideo"},{"$ref":"#/definitions/MediaAudio"}]},"MediaImage":{"description":"A MediaImage describes an image to be displayed in the browser. An image consists of one or more MediaSourceImage and additional metadata.","title":"MediaImage","id":"MediaImage","type":"object","properties":{"type":{"type":"string","const":"image"},"sources":{"type":"array","items":{"$ref":"#/definitions/MediaSourceImage"}},"alt":{"$ref":"#/definitions/__schema3"},"meta":{"$ref":"#/definitions/__schema4"},"aiDisclosure":{"$ref":"#/definitions/__schema5"},"placeholder":{"type":"array","items":[{"type":"string","enum":["solid","thumbhash"]},{"type":"string"}]}},"required":["type","sources"],"additionalProperties":false},"MediaSourceImage":{"description":"A MediaSourceImage is a single source for an image.","title":"MediaSourceImage","id":"MediaSourceImage","type":"object","properties":{"provider":{"$ref":"#/definitions/__schema0"},"src":{"$ref":"#/definitions/__schema1"},"origin":{"$ref":"#/definitions/__schema2"},"width":{"type":"number"},"height":{"type":"number"},"responsive":{"type":"string","enum":["static","mobile","desktop"]},"focalPoint":{"type":"array","items":[{"type":"number"},{"type":"number"}]}},"required":["provider","src"],"additionalProperties":false},"__schema0":{"type":"string"},"__schema1":{"type":"string"},"__schema2":{"type":"object","properties":{"libraryId":{"type":"string"},"externalId":{"type":"string"}},"required":["libraryId"],"additionalProperties":false},"__schema3":{"type":"string"},"__schema4":{},"__schema5":{"type":"string","enum":["generated","modified"]},"MediaVideo":{"description":"A MediaVideo describes a video to be displayed in the browser. A video consists of one or more MediaSourceVideo and additional metadata.","title":"MediaVideo","id":"MediaVideo","type":"object","properties":{"type":{"type":"string","const":"video"},"sources":{"type":"array","items":{"$ref":"#/definitions/MediaSourceVideo"}},"alt":{"$ref":"#/definitions/__schema3"},"meta":{"$ref":"#/definitions/__schema4"},"aiDisclosure":{"$ref":"#/definitions/__schema5"},"poster":{"allOf":[{"$ref":"#/definitions/MediaImage"}]},"tracks":{"type":"array","items":{"$ref":"#/definitions/MediaTextTrack"}}},"required":["type","sources"],"additionalProperties":false},"MediaSourceVideo":{"description":"A MediaSourceVideo is a single source for a video.","title":"MediaSourceVideo","id":"MediaSourceVideo","type":"object","properties":{"provider":{"$ref":"#/definitions/__schema0"},"src":{"$ref":"#/definitions/__schema1"},"origin":{"$ref":"#/definitions/__schema2"},"width":{"type":"number"},"height":{"type":"number"},"length":{"allOf":[{"$ref":"#/definitions/Duration"}]},"format":{"type":"string"},"responsive":{"type":"string","enum":["static","mobile","desktop"]},"focalPoint":{"type":"array","items":[{"type":"number"},{"type":"number"}]},"streaming":{"type":"string","enum":["progressive","hls","dash"]}},"required":["provider","src","width","height"],"additionalProperties":false},"Duration":{"description":"ISO 8601 duration string. Can be used with Temporal.Duration once browser-support is available. Maybe use https://react-spectrum.adobe.com/internationalized/date/Time.html","title":"Duration","id":"Duration","type":"object","properties":{"duration":{"type":"string"}},"required":["duration"],"additionalProperties":false},"MediaTextTrack":{"description":"A MediaTextTrack describes a timed text track for a video or audio asset, such as subtitles, captions, or chapters.","title":"MediaTextTrack","id":"MediaTextTrack","type":"object","properties":{"src":{"type":"string"},"kind":{"type":"string","enum":["subtitles","captions","descriptions","chapters","metadata"]},"srclang":{"type":"string"},"label":{"type":"string"},"default":{"type":"boolean"}},"required":["src","kind"],"additionalProperties":false},"MediaAudio":{"description":"A MediaAudio describes an audio asset to be played in the browser. An audio asset consists of one or more MediaSourceAudio and additional metadata.","title":"MediaAudio","id":"MediaAudio","type":"object","properties":{"type":{"type":"string","const":"audio"},"sources":{"type":"array","items":{"$ref":"#/definitions/MediaSourceAudio"}},"alt":{"$ref":"#/definitions/__schema3"},"meta":{"$ref":"#/definitions/__schema4"},"aiDisclosure":{"$ref":"#/definitions/__schema5"},"cover":{"allOf":[{"$ref":"#/definitions/MediaImage"}]},"tracks":{"type":"array","items":{"$ref":"#/definitions/MediaTextTrack"}}},"required":["type","sources"],"additionalProperties":false},"MediaSourceAudio":{"description":"A MediaSourceAudio is a single source for an audio asset.","title":"MediaSourceAudio","id":"MediaSourceAudio","type":"object","properties":{"provider":{"$ref":"#/definitions/__schema0"},"src":{"$ref":"#/definitions/__schema1"},"origin":{"$ref":"#/definitions/__schema2"},"format":{"type":"string"},"length":{"allOf":[{"$ref":"#/definitions/Duration"}]},"streaming":{"type":"string","enum":["progressive","hls","dash"]}},"required":["provider","src"],"additionalProperties":false},"Link":{"description":"A link to an internal or external page. Links can be of different types for different link-targets.","id":"Link","anyOf":[{"$ref":"#/definitions/LinkReference"},{"$ref":"#/definitions/LinkUrl"},{"$ref":"#/definitions/LinkPage"},{"$ref":"#/definitions/LinkAnchor"},{"$ref":"#/definitions/LinkPageType"}]},"LinkReference":{"description":"Link to a entity-detail-page.","id":"LinkReference","type":"object","properties":{"type":{"type":"string","const":"reference"},"query":{"$ref":"#/definitions/__schema6"},"reference":{"type":"object","properties":{"type":{},"slug":{"type":"string"},"id":{"type":"string"}},"required":["type","slug"],"additionalProperties":false}},"required":["type","reference"],"additionalProperties":false},"__schema6":{"type":"object","propertyNames":{"anyOf":[{"type":"string"},{"type":"number"}]},"additionalProperties":{"anyOf":[{"$ref":"#/definitions/__schema7"},{"type":"array","items":{"$ref":"#/definitions/__schema7"}}]}},"__schema7":{"description":"As we use vue-router, we need to adhere to its supported query types: https://router.vuejs.org/api/type-aliases/LocationQueryRaw.html","anyOf":[{"type":"string"},{"type":"number"},{"type":"null"}]},"LinkUrl":{"description":"Link to a given url directly.","id":"LinkUrl","type":"object","properties":{"type":{"type":"string","const":"url"},"query":{"$ref":"#/definitions/__schema6"},"href":{"type":"string"},"target":{"type":"string","enum":["_blank"]}},"required":["type","href"],"additionalProperties":false},"LinkPage":{"description":"Link to a specific page by its ID.","id":"LinkPage","type":"object","properties":{"type":{"type":"string","const":"page"},"query":{"$ref":"#/definitions/__schema6"},"page":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":false},"params":{"allOf":[{"$ref":"#/definitions/__schema8"}]}},"required":["type","page"],"additionalProperties":false},"__schema8":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"LinkAnchor":{"description":"Link to an anchor on the current page","id":"LinkAnchor","type":"object","properties":{"type":{"type":"string","const":"anchor"},"query":{"$ref":"#/definitions/__schema6"},"fragment":{"description":"Does _not_ contain the hash (#) prefix. See https://url.spec.whatwg.org/#concept-url-fragment","type":"string"}},"required":["type","fragment"],"additionalProperties":false},"LinkPageType":{"description":"Link to a page of a certain type. It's not guaranteed that a page of this type exists.","id":"LinkPageType","type":"object","properties":{"type":{"type":"string","const":"pageType"},"query":{"$ref":"#/definitions/__schema6"},"pageType":{"type":"string"},"params":{"allOf":[{"$ref":"#/definitions/__schema8"}]},"subject":{"type":"object","properties":{"type":{},"id":{"type":"string"}},"required":["type","id"],"additionalProperties":false}},"required":["type","pageType"],"additionalProperties":false}}}
```

### Media

All product images and videos, including the cover image at index 0.

### ProductMedia `media`

```ts
import { ProductMedia } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"images":{"description":"All images of a product, including the cover-image on index 0.","type":"array","items":{"$ref":"#/definitions/MediaImage"}},"media":{"description":"All media: images, videos, 3d models and other media.","type":"array","items":{"$ref":"#/definitions/Media"}}},"required":["images","media"],"additionalProperties":false,"id":"entity:Product:media","definitions":{"MediaImage":{"description":"A MediaImage describes an image to be displayed in the browser. An image consists of one or more MediaSourceImage and additional metadata.","title":"MediaImage","id":"MediaImage","type":"object","properties":{"type":{"type":"string","const":"image"},"sources":{"type":"array","items":{"$ref":"#/definitions/MediaSourceImage"}},"alt":{"$ref":"#/definitions/__schema3"},"meta":{"$ref":"#/definitions/__schema4"},"aiDisclosure":{"$ref":"#/definitions/__schema5"},"placeholder":{"type":"array","items":[{"type":"string","enum":["solid","thumbhash"]},{"type":"string"}]}},"required":["type","sources"],"additionalProperties":false},"MediaSourceImage":{"description":"A MediaSourceImage is a single source for an image.","title":"MediaSourceImage","id":"MediaSourceImage","type":"object","properties":{"provider":{"$ref":"#/definitions/__schema0"},"src":{"$ref":"#/definitions/__schema1"},"origin":{"$ref":"#/definitions/__schema2"},"width":{"type":"number"},"height":{"type":"number"},"responsive":{"type":"string","enum":["static","mobile","desktop"]},"focalPoint":{"type":"array","items":[{"type":"number"},{"type":"number"}]}},"required":["provider","src"],"additionalProperties":false},"__schema0":{"type":"string"},"__schema1":{"type":"string"},"__schema2":{"type":"object","properties":{"libraryId":{"type":"string"},"externalId":{"type":"string"}},"required":["libraryId"],"additionalProperties":false},"__schema3":{"type":"string"},"__schema4":{},"__schema5":{"type":"string","enum":["generated","modified"]},"Media":{"description":"A Media object describes a media asset to be displayed or played in the browser. Currently supported are images, videos, and audio. Future support for 3D, 360° images, and other media types is planned. You can use `type` for type-narrowing.","title":"Media","id":"Media","anyOf":[{"$ref":"#/definitions/MediaImage"},{"$ref":"#/definitions/MediaVideo"},{"$ref":"#/definitions/MediaAudio"}]},"MediaVideo":{"description":"A MediaVideo describes a video to be displayed in the browser. A video consists of one or more MediaSourceVideo and additional metadata.","title":"MediaVideo","id":"MediaVideo","type":"object","properties":{"type":{"type":"string","const":"video"},"sources":{"type":"array","items":{"$ref":"#/definitions/MediaSourceVideo"}},"alt":{"$ref":"#/definitions/__schema3"},"meta":{"$ref":"#/definitions/__schema4"},"aiDisclosure":{"$ref":"#/definitions/__schema5"},"poster":{"allOf":[{"$ref":"#/definitions/MediaImage"}]},"tracks":{"type":"array","items":{"$ref":"#/definitions/MediaTextTrack"}}},"required":["type","sources"],"additionalProperties":false},"MediaSourceVideo":{"description":"A MediaSourceVideo is a single source for a video.","title":"MediaSourceVideo","id":"MediaSourceVideo","type":"object","properties":{"provider":{"$ref":"#/definitions/__schema0"},"src":{"$ref":"#/definitions/__schema1"},"origin":{"$ref":"#/definitions/__schema2"},"width":{"type":"number"},"height":{"type":"number"},"length":{"allOf":[{"$ref":"#/definitions/Duration"}]},"format":{"type":"string"},"responsive":{"type":"string","enum":["static","mobile","desktop"]},"focalPoint":{"type":"array","items":[{"type":"number"},{"type":"number"}]},"streaming":{"type":"string","enum":["progressive","hls","dash"]}},"required":["provider","src","width","height"],"additionalProperties":false},"Duration":{"description":"ISO 8601 duration string. Can be used with Temporal.Duration once browser-support is available. Maybe use https://react-spectrum.adobe.com/internationalized/date/Time.html","title":"Duration","id":"Duration","type":"object","properties":{"duration":{"type":"string"}},"required":["duration"],"additionalProperties":false},"MediaTextTrack":{"description":"A MediaTextTrack describes a timed text track for a video or audio asset, such as subtitles, captions, or chapters.","title":"MediaTextTrack","id":"MediaTextTrack","type":"object","properties":{"src":{"type":"string"},"kind":{"type":"string","enum":["subtitles","captions","descriptions","chapters","metadata"]},"srclang":{"type":"string"},"label":{"type":"string"},"default":{"type":"boolean"}},"required":["src","kind"],"additionalProperties":false},"MediaAudio":{"description":"A MediaAudio describes an audio asset to be played in the browser. An audio asset consists of one or more MediaSourceAudio and additional metadata.","title":"MediaAudio","id":"MediaAudio","type":"object","properties":{"type":{"type":"string","const":"audio"},"sources":{"type":"array","items":{"$ref":"#/definitions/MediaSourceAudio"}},"alt":{"$ref":"#/definitions/__schema3"},"meta":{"$ref":"#/definitions/__schema4"},"aiDisclosure":{"$ref":"#/definitions/__schema5"},"cover":{"allOf":[{"$ref":"#/definitions/MediaImage"}]},"tracks":{"type":"array","items":{"$ref":"#/definitions/MediaTextTrack"}}},"required":["type","sources"],"additionalProperties":false},"MediaSourceAudio":{"description":"A MediaSourceAudio is a single source for an audio asset.","title":"MediaSourceAudio","id":"MediaSourceAudio","type":"object","properties":{"provider":{"$ref":"#/definitions/__schema0"},"src":{"$ref":"#/definitions/__schema1"},"origin":{"$ref":"#/definitions/__schema2"},"format":{"type":"string"},"length":{"allOf":[{"$ref":"#/definitions/Duration"}]},"streaming":{"type":"string","enum":["progressive","hls","dash"]}},"required":["provider","src"],"additionalProperties":false}}}
```

### Prices

Aggregate pricing across all variants: selling price, strikethrough price, sale indicators, and unit pricing.

### ProductPrices `prices`

```ts
import { ProductPrices } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"price":{"$ref":"#/definitions/Money"},"strikethroughPrice":{"allOf":[{"$ref":"#/definitions/Money"}]},"isOnSale":{"description":"If true, indicates that the product is discounted from its original price. See `strikethroughPrice` for the original price and `savingsPercent` for the percentage of discount.","type":"boolean"},"savingsPercent":{"description":"Percentage of discount from the `strikethroughPrice`.","type":"number"},"isStartingFrom":{"description":"If true, indicates that there are multiple variants with different prices. Can be used in the frontend to indicate a starting-from price, e.g. \"from 100 EUR\".","type":"boolean"},"unitPrice":{"allOf":[{"$ref":"#/definitions/UnitPrice"}]}},"required":["price","isOnSale","isStartingFrom"],"additionalProperties":false,"id":"entity:Product:prices","definitions":{"Money":{"description":"A Money object describes a monetary value. You can use e.g. `@screeny05/ts-money` to create and manipulate Money objects more elegantly.","title":"Money","id":"Money","type":"object","properties":{"amount":{"description":"Amount in smallest unit of currency. E.g. cents, pence, etc.","type":"number"},"currency":{"description":"ISO 4217 currency code. E.g. 'USD', 'EUR', 'GBP', etc.","type":"string"}},"required":["amount","currency"],"additionalProperties":false},"UnitPrice":{"description":"This object descibes a unit-price, e.g. 13.99 EUR / 100g","title":"UnitPrice","id":"UnitPrice","type":"object","properties":{"price":{"$ref":"#/definitions/Money"},"quantity":{"$ref":"#/definitions/Measurement"},"reference":{"$ref":"#/definitions/Measurement"}},"required":["price","quantity","reference"],"additionalProperties":false},"Measurement":{"description":"A value-object representing a measurement of a physical quantity. E.g. length, weight, etc.","title":"Measurement","id":"Measurement","type":"object","properties":{"unit":{"type":"string"},"value":{"type":"number"}},"required":["unit","value"],"additionalProperties":false}}}
```

### Rating

Average review score (1-5) and total review count.

### ProductRating `rating`

```ts
import { ProductRating } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"average":{"$ref":"#/definitions/Rating"},"count":{"description":"Number of ratings.","type":"number"}},"required":["average","count"],"additionalProperties":false,"id":"entity:Product:rating","definitions":{"Rating":{"description":"A rating between 1 and 5. That's the most common rating scale. No other scale is supported at the moment. Decimals are allowed.","title":"Rating","id":"Rating","type":"number","minimum":1,"maximum":5}}}
```

### SEO

Meta title, description, and robots directive for the product detail page.

### ProductSeo `seo`

```ts
import { ProductSeo } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","description":"SEO information for use in meta tags.","title":"SeoMeta","id":"SeoMeta","type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"robots":{"description":"E.g. \"index, follow\", \"noindex, nofollow\", etc.","type":"string"}},"additionalProperties":false}
```

### Analytics

Query parameters for PDP links and custom tracking fields.

### ProductAnalytics `analytics`

```ts
import { ProductAnalytics } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"pdpLinkQuery":{"description":"Query parameters to be added to the product detail page link.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"custom":{"allOf":[{"$ref":"#/definitions/CustomFields"}]}},"additionalProperties":false,"id":"entity:Product:analytics","definitions":{"CustomFields":{"description":"Custom fields are a way for implementations to pass arbitrary data to actions, queries, etc.","title":"CustomFields","id":"CustomFields","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}}
```

### Brand

Brand or manufacturer name with an optional link to the brand page.

### ProductBrand `brand`

```ts
import { ProductBrand } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"name":{"description":"Name of the brand.","type":"string"},"link":{"allOf":[{"$ref":"#/definitions/Link"}]}},"additionalProperties":false,"id":"entity:Product:brand","definitions":{"Link":{"description":"A link to an internal or external page. Links can be of different types for different link-targets.","id":"Link","anyOf":[{"$ref":"#/definitions/LinkReference"},{"$ref":"#/definitions/LinkUrl"},{"$ref":"#/definitions/LinkPage"},{"$ref":"#/definitions/LinkAnchor"},{"$ref":"#/definitions/LinkPageType"}]},"LinkReference":{"description":"Link to a entity-detail-page.","id":"LinkReference","type":"object","properties":{"type":{"type":"string","const":"reference"},"query":{"$ref":"#/definitions/__schema0"},"reference":{"type":"object","properties":{"type":{},"slug":{"type":"string"},"id":{"type":"string"}},"required":["type","slug"],"additionalProperties":false}},"required":["type","reference"],"additionalProperties":false},"__schema0":{"type":"object","propertyNames":{"anyOf":[{"type":"string"},{"type":"number"}]},"additionalProperties":{"anyOf":[{"$ref":"#/definitions/__schema1"},{"type":"array","items":{"$ref":"#/definitions/__schema1"}}]}},"__schema1":{"description":"As we use vue-router, we need to adhere to its supported query types: https://router.vuejs.org/api/type-aliases/LocationQueryRaw.html","anyOf":[{"type":"string"},{"type":"number"},{"type":"null"}]},"LinkUrl":{"description":"Link to a given url directly.","id":"LinkUrl","type":"object","properties":{"type":{"type":"string","const":"url"},"query":{"$ref":"#/definitions/__schema0"},"href":{"type":"string"},"target":{"type":"string","enum":["_blank"]}},"required":["type","href"],"additionalProperties":false},"LinkPage":{"description":"Link to a specific page by its ID.","id":"LinkPage","type":"object","properties":{"type":{"type":"string","const":"page"},"query":{"$ref":"#/definitions/__schema0"},"page":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":false},"params":{"allOf":[{"$ref":"#/definitions/__schema2"}]}},"required":["type","page"],"additionalProperties":false},"__schema2":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"LinkAnchor":{"description":"Link to an anchor on the current page","id":"LinkAnchor","type":"object","properties":{"type":{"type":"string","const":"anchor"},"query":{"$ref":"#/definitions/__schema0"},"fragment":{"description":"Does _not_ contain the hash (#) prefix. See https://url.spec.whatwg.org/#concept-url-fragment","type":"string"}},"required":["type","fragment"],"additionalProperties":false},"LinkPageType":{"description":"Link to a page of a certain type. It's not guaranteed that a page of this type exists.","id":"LinkPageType","type":"object","properties":{"type":{"type":"string","const":"pageType"},"query":{"$ref":"#/definitions/__schema0"},"pageType":{"type":"string"},"params":{"allOf":[{"$ref":"#/definitions/__schema2"}]},"subject":{"type":"object","properties":{"type":{},"id":{"type":"string"}},"required":["type","id"],"additionalProperties":false}},"required":["type","pageType"],"additionalProperties":false}}}
```

### Default Variant

Preferred variant ID or option values to preselect on the product detail page.

### ProductDefaultVariant `defaultVariant`

```ts
import { ProductDefaultVariant } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"origin":{"description":"Whether the backend chose this variant, or only named a candidate. `authored` — a merchant or a search result points at this variant on purpose. A consumer may surface that choice: preselect a swatch, call the variant out. `derived` — the connector had to name some variant and picked one, such as the first in stock or the first in the configurator. Use it as a target for add-to-cart, for `sku` in analytics, or for `status`. Never read it as a statement about what the shopper should see, because it moves as stock moves. An absent value means `derived`. A connector that declares no intent has none.","type":"string","enum":["authored","derived"]},"id":{"description":"The variant this product opens on. An `authored` origin makes it a merchant's preference, a `derived` origin makes it a candidate only. Respecting this property is up to the frontend components.","type":"string"},"options":{"description":"A default variant can also be specified by a list of option values instead of a single id. E.g. [\"clay\", \"small\"] or [\"red\", \"2xl\", \"cotton\"] Respecting this property is up to the frontend components.","type":"array","items":{"type":"string"}},"sku":{"description":"Stock-keeping unit of the named variant. Analytics destinations key an item on it, so a listing can report one without resolving any variant entity.","type":"string"},"status":{"description":"","type":"string","enum":["inStock","outOfStock","backorder","preorder"]}},"additionalProperties":false,"id":"entity:Product:defaultVariant"}
```

### Option Groups

The product's variant-selection axes and all possible values, such as Size → S, M, L and Color → Red, Blue. Available combinations are derived from the linked variants.

### ProductOptionGroups `optionGroups`

```ts
import { ProductOptionGroups } from '@laioutr-core/canonical-types/entity/product';
```

#### Schema

```json
{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"groups":{"type":"array","items":{"type":"object","properties":{"wellKnownName":{"type":"string"},"name":{"description":"Locale-resolved display name of the axis, e.g. \"Size\".","type":"string"},"values":{"description":"All selectable values on this axis.","type":"array","items":{"type":"object","properties":{"value":{"description":"Display value, e.g. \"M\" or \"Red\". Named to mirror {@link ProductVariantOptions} `selected[].value` — the join key the frontend correlates a variant back to this axis value on.","type":"string"},"variantId":{"description":"A variant that carries this value, when the source can name one without a further round-trip — the add-to-cart target for a single-axis selector (size-only apparel), where value ⇔ variant is 1:1. On a multi-axis product this is only a *representative* variant (the connector picks the lowest-position values on the other axes), so a selector that spans several axes must still resolve the real combination through a variant query. Absent ⇒ the source cannot name one; fall back to the variants. Says nothing about stock: a value can be sold out and still name a variant. Read `available` for that.","type":"string"},"available":{"description":"Whether at least one purchasable variant carries this value — lets a selector render an enabled/disabled state (and a tile show only buyable swatches) without resolving any variant. Connector-computed per single axis value, so it cannot express combination availability (\"Red + XL\"); that needs the variant query. Absent ⇒ unknown, treat as available.","type":"boolean"},"swatch":{"allOf":[{"$ref":"#/definitions/Swatch"}]},"image":{"allOf":[{"$ref":"#/definitions/MediaImage"}]}},"required":["value"],"additionalProperties":false}}},"required":["name","values"],"additionalProperties":false}}},"required":["groups"],"additionalProperties":false,"id":"entity:Product:optionGroups","definitions":{"Swatch":{"description":"A swatch is a visual representation of a product option. E.g. a single color, multiple colors, a gradient or a thumbnail image.","id":"Swatch","anyOf":[{"$ref":"#/definitions/SwatchColor"},{"$ref":"#/definitions/SwatchColors"},{"$ref":"#/definitions/SwatchGradient"},{"$ref":"#/definitions/SwatchImage"}]},"SwatchColor":{"description":"","id":"SwatchColor","type":"array","items":[{"type":"string","const":"color"},{"type":"string"}]},"SwatchColors":{"description":"","id":"SwatchColors","type":"array","items":[{"type":"string","const":"colors"},{"type":"array","items":{"type":"string"}}]},"SwatchGradient":{"description":"","id":"SwatchGradient","type":"array","items":[{"type":"string","const":"gradient"},{"type":"array","items":{"type":"string"}}]},"SwatchImage":{"description":"","id":"SwatchImage","type":"array","items":[{"type":"string","const":"image"},{"$ref":"#/definitions/MediaImage"}]},"MediaImage":{"description":"A MediaImage describes an image to be displayed in the browser. An image consists of one or more MediaSourceImage and additional metadata.","title":"MediaImage","id":"MediaImage","type":"object","properties":{"type":{"type":"string","const":"image"},"sources":{"type":"array","items":{"$ref":"#/definitions/MediaSourceImage"}},"alt":{"type":"string"},"meta":{},"aiDisclosure":{"type":"string","enum":["generated","modified"]},"placeholder":{"type":"array","items":[{"type":"string","enum":["solid","thumbhash"]},{"type":"string"}]}},"required":["type","sources"],"additionalProperties":false},"MediaSourceImage":{"description":"A MediaSourceImage is a single source for an image.","title":"MediaSourceImage","id":"MediaSourceImage","type":"object","properties":{"provider":{"type":"string"},"src":{"type":"string"},"origin":{"type":"object","properties":{"libraryId":{"type":"string"},"externalId":{"type":"string"}},"required":["libraryId"],"additionalProperties":false},"width":{"type":"number"},"height":{"type":"number"},"responsive":{"type":"string","enum":["static","mobile","desktop"]},"focalPoint":{"type":"array","items":[{"type":"number"},{"type":"number"}]}},"required":["provider","src"],"additionalProperties":false}}}
```

## Queries

- [ProductBySlug](/frontend/api-reference/ecommerce/queries#product) - Retrieve a single product by its slug.
- [ProductsByCategoryId](/frontend/api-reference/ecommerce/queries#product) - Retrieve multiple products by a category ID.
- [ProductsByCategorySlug](/frontend/api-reference/ecommerce/queries#product) - Retrieve multiple products by a category slug.
- [ProductSearch](/frontend/api-reference/ecommerce/queries#product) - Search for products using a query string.

## Links

- **ProductAllCategories** `Category` `multi` - All categories the product is assigned to.
- **ProductBreadcrumb** `BreadcrumbItem` `multi` - Breadcrumb items for the product detail page.
- **ProductReviews** `Review` `multi` - User-generated product reviews.
- **ProductVariants** `ProductVariant` `multi` - The actual buyable SKUs. A Product must always link to at least one ProductVariant.

## Related actions

- [CreateReview](/frontend/api-reference/ecommerce/actions#review) - Create a new user review for a product.

## Related Entities

- [Cart](/frontend/api-reference/entities/cart) - Shopping session that can contain this product's variants as line items.
- [MenuItem](/frontend/api-reference/entities/menu-item) - Navigation menu entries that may link to this product.
