Model A - Single Product

Learn how to set up a sample product data model to display a single electronic product including all its variants.

  • After completing this page, you should be able to:

    • Configure the product data model for a single product listing with its variants.
  • A key factor that you need to consider when deciding how to configure your product data model is to define how your product will be displayed on the Product detail, Product listing, and Search results pages. These decisions will impact the Product Type you create along with the Products and Product Variants.

    We will explore this process as part of this module on advanced product data modeling, and explain various solutions depending on different business requirements.

    We will use the iPhone 16 Product as a case study to learn about these decisions in more detail. In addition, we will analyze various iPhone 16 models to understand how to configure advanced product data modeling in Composable Commerce for different use cases.

    The iPhone has a hierarchical structure of variations, starting with the base model and progressing through increasingly specific attributes:

    • Base model: iPhone 16
      • Model variant: Base (6.1 inch)
        • colors (5)
          • storage (3)
      • Model variant: Plus (6.7 inch)
        • colors (5)
          • storage (3)
      • Model variant: Pro (6.3 inch)
        • colors (4)
          • storage (4)
      • Model variant: Max (6.9 inch)
        • colors (4)
          • storage (3)

    Now let’s explore how we can model the iPhone 16 Product in Composable Commerce.

    Model A: Single product listing with full variant display

    Our first product data model needs to meet the following requirements:

    • Product listing page: display as a single Product.

    Model A product listing page.

    • Search result: display as a single Product.

    Model A search results page.

    • Product detail page: display all Product combinations.

    Model A product detail page.

    Set up Product data model in Composable Commerce

    To fulfill the requirements of setting up our product data model in Composable Commerce, we need to configure the following settings and understand the resulting impact:

    • Number of Products: 1
    • Number of URLs: 1
    • Number of Product Variants per Product: 58
    • Price display: We will need to use a price range on the Product card (showing the lowest and highest price across all variants $1,399.00 - 2,849.00 AUD).

    We can meet the above requirements by creating a Product Type with the following listed Product Attributes:

    Product Type and Product Attributes summary

    AttributeLabelTypeRequiredSearchablePossible Values
    colorcolorenumTrueTrueUltramarine, Teal, Pink, White, Black, Black Titanium, White Titanium, Natural Titanium, Desert Titanium
    storagestoragenumberTrueTrue128000, 256000, 512000, 1000000
    displayDisplay SizeenumTrueTrue6.1, 6.3, 6.7, 6.9
    display-typeDisplay TypeenumFalseFalseSuper Retina XDR
    promotionProMotion technologybooleanFalseTrue
    always-on-displayAlways-On DisplaybooleanFalseTrue
    processorProcessorenumFalseFalseA18, A18 Pro
    gpu-coresGPU CoresnumberFalseFalse
    camera-controlCamera ControlbooleanFalseFalse
    camera-systemCamera SystemenumFalseFalseAdvanced dual, Pro
    main-cameraMain CameraenumFalseFalse48MP Fusion
    telephotoTelephotonumberFalseFalse
    megapixelsMegapixelsnumberFalseFalse
    emergency-sosEmergency SOSbooleanFalseFalse
    emergency-satellite-sosEmergency Satellite SOSbooleanFalseFalse
    video-playbackVideo PlaybacktextFalseFalse
    usb-versionUSB VersionenumFalseFalse2, 3
    cellularCellularenumFalseFalse5G
    modelModelenumFalseFalseBase, Plus, Pro, Max

    Product Type JSON

    The Product Type JSON object is shown here. You can use this JSON object in your own Project to create the same Product Type.

    {
    "id": "7bf93cac-94ed-44ad-b8c0-91fb67103f42",
    "version": 1,
    "versionModifiedAt": "2024-10-30T02:18:49.579Z",
    "createdAt": "2024-10-30T02:18:49.579Z",
    "lastModifiedAt": "2024-10-30T02:18:49.579Z",
    "lastModifiedBy": {
    "clientId": "ViXgKzquNw-cXDYLmXagUH3Q",
    "isPlatformClient": false
    },
    "createdBy": {
    "clientId": "ViXgKzquNw-cXDYLmXagUH3Q",
    "isPlatformClient": false
    },
    "name": "iphone-a",
    "description": "iphone-a",
    "classifier": "Complex",
    "attributes": [
    {
    "name": "color",
    "label": {
    "en-AU": "color",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": true,
    "type": {
    "name": "enum",
    "values": [
    {
    "key": "ultramarine",
    "label": "Ultramarine"
    },
    {
    "key": "teal",
    "label": "Teal"
    },
    {
    "key": "pink",
    "label": "Pink"
    },
    {
    "key": "white",
    "label": "White"
    },
    {
    "key": "black",
    "label": "Black"
    },
    {
    "key": "black-titanium",
    "label": "Black Titanium"
    },
    {
    "key": "white-titanium",
    "label": "White Titanium"
    },
    {
    "key": "natural-titanium",
    "label": "Natural Titanium"
    },
    {
    "key": "desert-titanium",
    "label": "Desert Titanium"
    }
    ]
    },
    "attributeConstraint": "CombinationUnique",
    "isSearchable": true,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "storage",
    "label": {
    "en-AU": "storage",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": true,
    "type": {
    "name": "number"
    },
    "attributeConstraint": "CombinationUnique",
    "isSearchable": true,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "display",
    "label": {
    "en-AU": "Display Size",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": true,
    "type": {
    "name": "enum",
    "values": [
    {
    "key": "6.1",
    "label": "6.1"
    },
    {
    "key": "6.3",
    "label": "6.3"
    },
    {
    "key": "6.7",
    "label": "6.7"
    },
    {
    "key": "6.9",
    "label": "6.9"
    }
    ]
    },
    "attributeConstraint": "CombinationUnique",
    "isSearchable": true,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "display-type",
    "label": {
    "en-AU": "Display Type",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "enum",
    "values": [
    {
    "key": "super-retina-xdr",
    "label": "Super Retina XDR"
    }
    ]
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "promotion",
    "label": {
    "en-AU": "ProMotion technology",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "boolean"
    },
    "attributeConstraint": "None",
    "isSearchable": true,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "always-on-display",
    "label": {
    "en-AU": "Always-On Display",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "boolean"
    },
    "attributeConstraint": "None",
    "isSearchable": true,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "processor",
    "label": {
    "en-AU": "Processor",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "enum",
    "values": [
    {
    "key": "a18",
    "label": "A18"
    },
    {
    "key": "a18-pro",
    "label": "A18 Pro"
    }
    ]
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "gpu-cores",
    "label": {
    "en-AU": "GPU Cores",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "number"
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "camera-control",
    "label": {
    "en-AU": "Camera Control",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "boolean"
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "camera-system",
    "label": {
    "en-AU": "Camera System",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "enum",
    "values": [
    {
    "key": "advanced-dual",
    "label": "Advanced dual"
    },
    {
    "key": "pro",
    "label": "Pro"
    }
    ]
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "main-camera",
    "label": {
    "en-AU": "Main Camera",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "enum",
    "values": [
    {
    "key": "48mp-fusion",
    "label": "48MP Fusion"
    }
    ]
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "telephoto",
    "label": {
    "en-AU": "Telephoto",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "number"
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "megapixels",
    "label": {
    "en-AU": "Megapixels",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "number"
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "emergency-sos",
    "label": {
    "en-AU": "Emergency SOS",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "boolean"
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "emergency-satellite-sos",
    "label": {
    "en-AU": "Emergency Satellite SOS",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "boolean"
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "video-playback",
    "label": {
    "en-AU": "Video Playback",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "text"
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "usb-version",
    "label": {
    "en-AU": "USB Version",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "enum",
    "values": [
    {
    "key": "2",
    "label": "2"
    },
    {
    "key": "3",
    "label": "3"
    }
    ]
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "cellular",
    "label": {
    "en-AU": "Cellular",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "enum",
    "values": [
    {
    "key": "5g",
    "label": "5G"
    }
    ]
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    },
    {
    "name": "model",
    "label": {
    "en-AU": "Model",
    "en-NZ": "",
    "zh": ""
    },
    "inputTip": {
    "en-AU": "",
    "en-NZ": "",
    "zh": ""
    },
    "isRequired": false,
    "type": {
    "name": "enum",
    "values": [
    {
    "key": "base",
    "label": "Base"
    },
    {
    "key": "plus",
    "label": "Plus"
    },
    {
    "key": "pro",
    "label": "Pro"
    },
    {
    "key": "max",
    "label": "Max"
    }
    ]
    },
    "attributeConstraint": "None",
    "isSearchable": false,
    "inputHint": "SingleLine",
    "displayGroup": "Other"
    }
    ],
    "key": "iphone-a"
    }

    iPhone 16 Product JSON example

    Here's the JSON of the Product that we need to create in our module: iPhone 16 Product JSON.

    • Total: 252 KB
    • Per Projection (each Staged and Current): 126 KB each
    • Variants: 124 KB
    • Attributes: 48.7 KB
    • Assets: 50.4 KB
    • Prices: 20.0 KB
  • Attribute decisions

    In this use case, we have chosen the storage capacity to be modeled by using a number attribute with a consistent unit of measure, specifically megabytes (MB). While iPhones offer storage options such as 125 GB, 256 GB, 512 GB, and 1 TB, storing these values as their megabyte equivalents (for example, 128000 MB, 256000 MB, 512000 MB, and 1024000 MB) provides us greater flexibility in filter and query operations.

    This approach enables range-based searches, which help to filter products on Product listing pages and search results. For example, a customer could search for devices with storage greater than 200000 MB (effectively 200 GB), which wouldn't be possible with mixed units like GB and TB. To ensure accurate comparisons and to simplify queries, all storage values in this project are measured in megabytes (MB) - the smallest common unit.

    While the underlying data uses MB, you can easily and dynamically convert the displayed values for customers to more user-friendly units like GB or TB at the time of presentation. This provides a seamless customer experience while maintaining the backend's query capabilities. You can manage this conversion either in the frontend application logic, BFF, or some other layer. Alternatively, you could use a separate attribute to determine the displayed value, while still using storage for query operations.

    We have used a similar strategy for megapixels, telephoto, and gpu-cores; as a result, they are of type number.

    usb-version is of type enum because neither does the usb standard follow a strict numerical naming convention nor does it follow a clear naming convention. For example, USB 2.0, USB 3.0, USB 3.1 Gen 2, USB4, and USB4 Version 2.0 are all valid versions. This field could also be of type text, but the benefit of using enum is that we are ensuring that only valid values are used for the product. This is useful if Product data is updated by personnel across the Merchant Center. We have configured cellular and display in the same manner for similar reasons.

    We use enum values for display-type, model, color, processor, main-camera, and camera-system to ensure data integrity. Although display represents screen size in inches, it is also an enum. This helps include foldable phones with two screen sizes, storing both within a single attribute for simplified display. If filtering by screen size becomes necessary, we can refactor the display into separate attributes for standard and foldable phones.

    promotion, always-on-display, camera-control, emergency-sos, and emergency-satellite-sos are all boolean values.

    Note that display is an enum field that stores number values. In this use case, it is not required to query this field with the same level of flexibility as is necessary for storage. We can meet these requirements by using either type number or enum.

    video-playback is of type text because there can be large variations in the string values. For example, up to 33 hours, 16 hours of video playback, and 20 hours of high power mode.

    Attribute constraints

    A specific Product Variant is a combination of color, storage, and display. These are the important differentiating attributes for Product Variants. As a result, each Product Variant contains the CombinationUnique constraint.

    Attribute setting: Searchable

    You might have noticed in the earlier sections that we have made only a few of the Product Attributes searchable. This is to ensure:

    Searchable attribute optimization: marking only essential attributes as isSearchable improves the speed of Product Search indexing.

    The isSearchable attribute only affects Product Projection Search and Product Search endpoints, not Products or Product Projections.

    Note: For optimal performance with large or low-latency Product or Product Projection queries, refer to our indexes and query optimization guide.

    Test your knowledge