# Nested Attribute type The [AttributeNestedType](/urn?urn=ctp%3Aapi%3Atype%3AAttributeNestedType) lets you model tabular, structured data for Products. You can define a Product Type with custom Attributes and then nest that Product Type into another, composing advanced data structures from reusable building blocks. This feature is in [public beta](/offering/compatibility.md#public-beta) and has certain [caveats](/learning-model-your-product-catalog/attribute-types-and-attribute-groups/nested-attribute-type.md#caveats) to be aware of. ## Use case: structured nutrition data Consider a shop that offers food products. Many countries require nutrition facts (sugar, fat, protein quantities) for each product. A structured data object that holds nutrient information and can be reused across Products is a good fit for nested types. ![A tub of ice cream with nutrition facts label.](https://docs.commercetools.com/learning-model-your-product-catalog/images/ice-cream-nutrition.jpg) ## Create the base Product Type The objective is to construct a `food-product-type` that contains nutrition information captured in a [Product Type](/api/projects/productTypes.md#producttype) called `nutrient-information`. The `food-product-type` contains a [Set](/api/projects/productTypes.md#attributesettype) of `nutrient-information` objects (one for fat, one for sugar, and so on). ![Diagram showing the food-product-type containing a nested set of nutrient-information.](https://docs.commercetools.com/learning-model-your-product-catalog/images/food-product-type.png) Start by creating the base Product Type that you nest into the advanced Product Type. You must create the base Product Type first because you need its `id` to set up the reference relationship. The `nutrient-information` Product Type contains two Attributes: `quantityContained` (a number indicating the quantity) and `nutrientTypeCode` (a text value for the nutrient type). Both are set as required (`isRequired: true`). Nested Attributes are not searchable. Set `isSearchable` to `false` for all Attributes in the nested Product Type. ```json { "name": "nutrient-information", "description": "The nutrient-information product type.", "attributes": [ { "name": "quantityContained", "type": { "name": "number" }, "isRequired": true, "attributeConstraint": "None", "isSearchable": false, "label": { "en": "quantity contained" } }, { "name": "nutrientTypeCode", "type": { "name": "text" }, "isRequired": true, "attributeConstraint": "None", "isSearchable": false, "label": { "en": "nutrient type code" } } ] } ``` ```java // Attributes within the simple ProductType List attributes = List.of( AttributeDefinitionDraft .builder() .name("quantityContained") .type(AttributeNumberType.builder().build()) .isRequired(true) .attributeConstraint(AttributeConstraintEnum.NONE) .isSearchable(false) .label( LocalizedString.builder().addValue("en", "quantity contained").build() ) .build(), AttributeDefinitionDraft .builder() .name("nutrientTypeCode") .type(AttributeTextType.builder().build()) .isRequired(true) .attributeConstraint(AttributeConstraintEnum.NONE) .isSearchable(false) .label( LocalizedString.builder().addValue("en", "nutrient type Code").build() ) .build() ); // Create the ProductTypeDraft with the attributes final ProductTypeDraft simpleProductTypeDraft = ProductTypeDraft .builder() .name("nutrient-information") .description("The nutrient-information product type.") .attributes(attributes) .build(); // Create and return the simple Product Type final ProductType simpleProductType = apiRoot .productTypes() .post(simpleProductTypeDraft) .executeBlocking() .getBody(); ``` ```ts const simpleProductType = apiRoot .productTypes() .post({ body: { name: "nutrient-information", description: "The nutrient-information product type.", attributes: [{ name: "quantityContained", type: { name: "number" }, isRequired: true, attributeConstraint: "None", isSearchable: false, label: { "en": "quantity contained" } }, { name: "nutrientTypeCode", type: { name: "text" }, isRequired: true, attributeConstraint: "None", isSearchable: false, label: { "en": "nutrient type Code" } }] } }) .execute() .then(function ({ body }) { console.log(JSON.stringify(body)); }); ``` ```php add( AttributeDefinitionDraftBuilder::of() ->withName('quantityContained') ->withType(AttributeNumberTypeBuilder::of()->build()) ->withIsRequired(true) ->withAttributeConstraint('None') ->withIsSearchable(false) ->withLabel( LocalizedStringBuilder::of() ->put('en', 'quantity contained') ->build(), ) ->build(), ) ->add( AttributeDefinitionDraftBuilder::of() ->withName('nutrientTypeCode') ->withType(AttributeTextTypeBuilder::of()->build()) ->withIsRequired(true) ->withAttributeConstraint('None') ->withIsSearchable(false) ->withLabel( LocalizedStringBuilder::of() ->put('en', 'nutrient type Code') ->build(), ) ->build(), ); // Create the ProductTypeDraft with the attributes $simpleProductTypeDraft = ProductTypeDraftBuilder::of() ->withName('nutrient-information') ->withDescription('The nutrient-information product type.') ->withAttributes($attributes) ->build(); // Create and return the simple Product Type $simpleProductType = $apiRoot ->productTypes() ->post($simpleProductTypeDraft) ->execute(); ``` ```cs // Attributes within the simple ProductType var attributes = new List() { new AttributeDefinitionDraft() { Name = "quantityContained", Type = new AttributeNumberType(), IsRequired = true, AttributeConstraint = IAttributeConstraintEnum.None, IsSearchable = false, Label = new LocalizedString() { { "en", "quantity contained" } } }, new AttributeDefinitionDraft() { Name = "nutrientTypeCode", Type = new AttributeTextType(), IsRequired = true, AttributeConstraint = IAttributeConstraintEnum.None, IsSearchable = false, Label = new LocalizedString() { { "en", "nutrient type Code" } } } }; // Create the ProductTypeDraft with the attributes var simpleProductTypeDraft = new ProductTypeDraft() { Name = "nutrient-information", Description = "The nutrient-information product type.", Attributes = attributes }; // Create and return the simple Product Type var simpleProductType = projectApiRoot .ProductTypes() .Post(simpleProductTypeDraft) .ExecuteAsync() .Result; ``` ```sh #!/bin/sh curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.{region}.commercetools.com/{projectKey}/product-types -d @- << EOF { "name": "nutrient-information", "description": "The nutrient-information product type.", "attributes": [ { "name": "quantityContained", "type": { "name": "number" }, "isRequired": true, "attributeConstraint": "None", "isSearchable": false, "label": { "en": "quantity contained" } }, { "name": "nutrientTypeCode", "type": { "name": "text" }, "isRequired": true, "attributeConstraint": "None", "isSearchable": false, "label": { "en": "nutrient type Code" } } ] } EOF ``` ## Create the advanced Product Type After creating the base Product Type, create the `food-product-type` by referencing `nutrient-information` in a nested Attribute called `nutrients`. Add an additional `taste` Attribute for a textual description. ```json { "name": "food-product-type", "description": "The food product type.", "attributes": [ { "name": "taste", "type": { "name": "text" }, "isRequired": true, "attributeConstraint": "None", "isSearchable": false, "label": { "en": "taste" } }, { "name": "nutrients", "type": { "name": "set", "elementType": { "name": "nested", "typeReference": { "id": "", "typeId": "product-type" } } }, "isRequired": false, "attributeConstraint": "None", "isSearchable": false, "label": { "en": "food nutrients" } } ] } ``` ```java // Create a List of Attributes containing a set of nested Attributes List nestedAttributes = List.of( AttributeDefinitionDraft .builder() .name("taste") .type(AttributeTextType.builder().build()) .isRequired(true) .attributeConstraint(AttributeConstraintEnum.NONE) .isSearchable(false) .label(LocalizedString.builder().addValue("en", "taste").build()) .build(), AttributeDefinitionDraft .builder() .name("nutrients") .type( AttributeSetType .builder() .elementType( AttributeNestedType .builder() .typeReference( ProductTypeReference .builder() .id("") .build() ) .build() ) .build() ) .isRequired(false) .attributeConstraint(AttributeConstraintEnum.NONE) .isSearchable(false) .label(LocalizedString.builder().addValue("en", "food nutrients").build()) .build() ); // Create the ProductTypeDraft with the nested attributes final ProductTypeDraft advancedProductTypeDraft = ProductTypeDraft .builder() .name("food-product-type") .description("The food product type.") .attributes(nestedAttributes) .build(); // Create and return the advanced Product Type final ProductType advancedProductType = apiRoot .productTypes() .post(advancedProductTypeDraft) .executeBlocking() .getBody(); ``` ```ts const advancedProductType = apiRoot .productTypes() .post({ body: { name: "food-product-type", description: "The food product type.", attributes: [{ name: "taste", type: { name: "text" }, isRequired: true, attributeConstraint: "None", isSearchable: false, label: { "en": "taste" } }, { name: "nutrients", type: { name: "set", elementType: { name: "nested", typeReference: { id: "", typeId: "product-type" } } }, isRequired: false, attributeConstraint: "None", isSearchable: false, label: { "en": "food nutrients" } }] } }).execute() .then(function ({ body }) { console.log(JSON.stringify(body)); }); ``` ```php add( AttributeDefinitionDraftBuilder::of() ->withName('taste') ->withType(AttributeTextTypeBuilder::of()->build()) ->withIsRequired(true) ->withAttributeConstraint('None') ->withIsSearchable(false) ->withLabel( LocalizedStringBuilder::of() ->put('en', 'taste') ->build(), ) ->build(), ) ->add( AttributeDefinitionDraftBuilder::of() ->withName('nutrients') ->withType( AttributeSetTypeBuilder::of() ->withElementType( AttributeNestedTypeBuilder::of() ->withTypeReference( ProductTypeReferenceBuilder::of() ->withId('') ->build(), ) ->build(), ) ->build(), ) ->withIsRequired(false) ->withAttributeConstraint('None') ->withIsSearchable(false) ->withLabel( LocalizedStringBuilder::of() ->put('en', 'food nutrients') ->build(), ) ->build(), ); // Create the ProductTypeDraft with the nested attributes $advancedProductTypeDraft = ProductTypeDraftBuilder::of() ->withName('food-product-type') ->withDescription('The food product type.') ->withAttributes($nestedAttributes) ->build(); // Create and return the advanced Product Type $advancedProductType = $apiRoot ->productTypes() ->post($advancedProductTypeDraft) ->execute(); ``` ```cs // Create a List of Attributes containing a set of nested Attributes var nestedAttributes = new List() { new AttributeDefinitionDraft() { Name = "taste", Type = new AttributeTextType(), IsRequired = true, AttributeConstraint = IAttributeConstraintEnum.None, IsSearchable = false, Label = new LocalizedString() { { "en", "taste" } } }, new AttributeDefinitionDraft() { Name = "nutrients", Type = new AttributeSetType() { ElementType = new AttributeNestedType() { Name = "nested", TypeReference = new ProductTypeReference() { Id="" } } }, IsRequired = false, AttributeConstraint = IAttributeConstraintEnum.None, IsSearchable = false, Label = new LocalizedString() { { "en", "food nutrients" } } } }; // Create the ProductTypeDraft with the nested attributes var advancedProductTypeDraft = new ProductTypeDraft() { Name = "food-product-type", Description = "The food product type.", Attributes = nestedAttributes }; // Create and return the advanced Product Type var advancedProductTypeResult = projectApiRoot .ProductTypes() .Post(advancedProductTypeDraft) .ExecuteAsync() .Result; ``` ```sh #!/bin/sh curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.{region}.commercetools.com/{projectKey}/product-types -d @- << EOF { "name": "food-product-type", "description": "The food product type.", "attributes": [ { "name": "taste", "type": { "name": "text" }, "isRequired": true, "attributeConstraint": "None", "isSearchable": false, "label": { "en": "taste" } }, { "name": "nutrients", "type": { "name": "set", "elementType": { "name": "nested", "typeReference": { "id": "", "typeId": "product-type" } } }, "isRequired": false, "attributeConstraint": "None", "isSearchable": false, "label": { "en": "food nutrients" } } ] } EOF ``` ## Create a Product with nested Attributes With both Product Types in place, [create a Product](/urn?urn=ctp%3Aapi%3Aendpoint%3A%2F%7BprojectKey%7D%2Fproducts%3APOST) of the `food-product-type`. The following example creates a Product with `taste` set to `excellent` and two nested `nutrient-information` entries: 1.4 units of `FAT` and 1.15 units of `SUGAR`. ![Diagram showing the example-food-product with nested nutrient data.](https://docs.commercetools.com/learning-model-your-product-catalog/images/example-food-product.png) ```json { //... "attributes": [ { "name": "taste", "value": "excellent" }, { "name": "nutrients", "value": [ [ { "name": "quantityContained", "value": 1.4 }, { "name": "nutrientTypeCode", "value": "FAT" } ], [ { "name": "quantityContained", "value": 1.15 }, { "name": "nutrientTypeCode", "value": "SUGAR" } ] ] } ] //... } ``` ```java // Create a ProductDraft containing the advanced ProductType and values for the Attributes final ProductDraft productDraft = ProductDraft .builder() .name( LocalizedString.builder().addValue("en", "example-food-product").build() ) .slug( LocalizedString .builder() .addValue("en", "example-food-product-slug") .build() ) .productType( ProductTypeResourceIdentifier.builder().id("").build() ) .masterVariant( ProductVariantDraft .builder() .sku("SKU-example-food-product-1") .attributes( List.of( Attribute.builder().name("taste").value("excellent").build(), Attribute .builder() .name("nutrients") // Nested values are within a list of a list of Attributes .value( List.of( List.of( Attribute .builder() .name("quantityContained") .value(1.4) .build(), Attribute .builder() .name("nutrientTypeCode") .value("FAT") .build() ), List.of( Attribute .builder() .name("quantityContained") .value(1.15) .build(), Attribute .builder() .name("nutrientTypeCode") .value("SUGAR") .build() ) ) ) .build() ) ) .build() ) .build(); // Create and return the Product final Product newProduct = apiRoot .products() .post(productDraft) .executeBlocking() .getBody(); ``` ```ts const newProduct = apiRoot .products() .post({ body: { name: { "en": "example-food-product" }, slug: { "en": "example-food-product-slug" }, productType: { typeId: "product-type", id: "" }, masterVariant: { sku: "SKU-example-food-product-1", attributes: [ { name: "taste", value: "excellent" }, { name: "nutrients", value: [ [ { name: "quantityContained", value: 1.4 }, { name: "nutrientTypeCode", value: "FAT" } ], [ { name: "quantityContained", value: 1.15 }, { name: "nutrientTypeCode", value: "SUGAR" } ] ] } ] } } }) .execute() .then(function ({ body }) { console.log(JSON.stringify(body)); }); ``` ```php withName( LocalizedStringBuilder::of() ->put('en', 'example-food-product') ->build(), ) ->withSlug( LocalizedStringBuilder::of() ->put('en', 'example-food-product-slug') ->build(), ) ->withProductType( ProductTypeResourceIdentifierBuilder::of() ->withId('') ->build(), ) ->withMasterVariant( ProductVariantDraftBuilder::of() ->withSku('SKU-example-food-product-1') ->withAttributes( AttributeCollection::of() ->add( AttributeBuilder::of() ->withName('taste') ->withValue('excellent') ->build(), ) ->add( AttributeBuilder::of() ->withName('nutrients') ->withValue([ AttributeCollection::of() ->add( AttributeBuilder::of() ->withName('quantityContained') ->withValue(1.4) ->build(), ) ->add( AttributeBuilder::of() ->withName('nutrientTypeCode') ->withValue('FAT') ->build(), ), AttributeCollection::of() ->add( AttributeBuilder::of() ->withName('quantityContained') ->withValue(1.15) ->build(), ) ->add( AttributeBuilder::of() ->withName('nutrientTypeCode') ->withValue('SUGAR') ->build(), ), ]) ->build(), ), ) ->build(), ) ->build(); // Create and return the Product $newProduct = $apiRoot ->products() ->post($productDraft) ->execute(); ``` ```cs // Create a ProductDraft containing the advanced ProductType and values for the Attributes var productDraft = new ProductDraft() { Name = new LocalizedString() { { "en", "example-food-product" } }, Slug = new LocalizedString() { { "en", "example-food-product-slug" } }, ProductType = new ProductTypeResourceIdentifier() { Id = "" }, MasterVariant = new ProductVariantDraft() { Sku = "SKU-example-food-product-1", Attributes = new List() { new commercetools.Sdk.Api.Models.Products.Attribute() { Name = "taste", Value = "excellent" }, new commercetools.Sdk.Api.Models.Products.Attribute() { Name = "nutrients", // Nested values are within a list of a list of Attributes Value = new List>() { new List() { new commercetools.Sdk.Api.Models.Products.Attribute() { Name = "quantityContained", Value = 1.4 }, new commercetools.Sdk.Api.Models.Products.Attribute() { Name = "nutrientTypeCode", Value = "FAT" }, }, new List() { new commercetools.Sdk.Api.Models.Products.Attribute() { Name = "quantityContained", Value = 1.15 }, new commercetools.Sdk.Api.Models.Products.Attribute() { Name = "nutrientTypeCode", Value = "SUGAR" }, }, } } } } }; // Create and return the Product var newProduct = projectApiRoot .Products() .Post(productDraft) .ExecuteAsync() .Result; ``` ```sh #!/bin/sh curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.{region}.commercetools.com/{projectKey}/products -d @- << EOF { "productType": { "typeId":"product-type", "id":"" }, "name": { "en": "example-food-product" }, "slug": { "en": "example_food_product" }, "masterVariant": { "sku": "SKU-example-food-product-1", "attributes": [ { "name": "taste", "value": "excellent" }, { "name": "nutrients", "value": [ [ { "name": "quantityContained", "value": 1.4 }, { "name": "nutrientTypeCode", "value": "FAT" } ], [ { "name": "quantityContained", "value": 1.15 }, { "name": "nutrientTypeCode", "value": "SUGAR" } ] ] } ] } } EOF ``` ## How nesting works The AttributeNestedType lets you compose advanced Product Types from existing ones. A Product Type is defined once and referenced inside an [Attribute Definition](/api/projects/productTypes.md#attributedefinition) of another Product Type. ![Diagram showing a ProductType nested into an advanced ProductType.](https://docs.commercetools.com/learning-model-your-product-catalog/images/nested-product-type.png) The following JSON snippet shows how an existing ProductType is nested into an Attribute Definition: ```json { "name": "nestedProductType", "type": { "name": "nested", "typeReference": { "id": "", "typeId": "product-type" } } } ``` ## Caveats - The feature is in [public beta](/offering/compatibility.md#public-beta) and is subject to change. Use it carefully in production. - Values of nested type Attributes are not searchable. They can't be discovered through the [Product Search](/api/projects/product-search.md) or the [Product Projection Search](/api/projects/product-projection-search.md) API. - An iteration of [AttributeSetType](/urn?urn=ctp%3Aapi%3Atype%3AAttributeSetType) that terminates with an [AttributeNestedType](/urn?urn=ctp%3Aapi%3Atype%3AAttributeNestedType) is limited to five steps. - You are allowed to nest AttributeNestedTypes into other AttributeNestedTypes, but query performance is not guaranteed for deep nesting. - When [setting nested Attribute values](/api/projects/products.md#set-attribute) on Products, you must provide all Attribute values of the nested structure. Partial updates are not supported. - You can't nest Product Attributes into Product Types. Nested Attributes can't be used for Product Attributes. ## Related pages - [Area overview page with navigation](/learning-model-your-product-catalog.md) - [Previous page: Attribute types](/learning-model-your-product-catalog/attribute-types-and-attribute-groups/attribute-types.md) - [Next page: Attribute Groups](/learning-model-your-product-catalog/attribute-types-and-attribute-groups/attribute-groups.md)