#How to collect product variations

Use case:
App Workflow
PIM Features:
Product Models
Variant Products
REST API endpoint(s):
products product models family variants
If you're following our App workflow, make sure you previously read:

#Context

In the PIM we handle product models and product variations.

scheme_variants

Before digging into the code you can find out more about these concepts in our helpcenter

Here are quick definitions:

A product model gathers products with many similarities but a few differences - these differences are called “variations” and are defined by an associated family variant.

Example: T-shirts with the same material (”wool”), brand (”a brand”), and weight (”16 grams”); but with different sizes and colors.

A product variant is associated with a product model. It contains the common attribute values of its product model plus values for variations.

Example: A t-shirt with material = ”whool”, brand = ”a brand”, weight = ”16 grams”, size = “XL”, and color = “blue”.

And because a relation schema is worth a thousand words:

scheme_model_and_variant

Get the big picture here.

#Collect product variations

In this tutorial, we will introduce you to the two use cases you may encounter for retrieving products with variations.

  • Use case 1: Collect all product variation information, just as they are in the PIM (up to 2 possible levels)
  • Use case 2: Collect all product variation information on 1 level only)

The steps to follow are basically the same:

  1. get the product model
  2. get its family variant (to get the variation information)
  3. Retrieve the product variants associated to this product model

Only for use-case 2, you need to put on a single axis the variations once the product model and its family variant have been retrieved, and then get the associated product variants.

#Workflow

schema_product_models

#0 - Initialization


    function buildApiClient(): GuzzleHttp\Client
    {
        $pimUrl = 'https://url-of-your-pim.com';
        $appToken = 'your_app_token'; // Token provided during oAuth steps
    
        // If you haven't done it yet,
        // please follow the Guzzle official documentation to install the client
        // https://docs.guzzlephp.org/en/stable/overview.html#installation
    
        return new GuzzleHttp\Client([
            'base_uri' => $pimUrl,
            'headers' => ['Authorization' => 'Bearer ' . $appToken],
        ]);
    }
    
    

    // Install the node-fetch library by following the official documentation:
    // https://www.npmjs.com/package/node-fetch
    import fetch from 'node-fetch';
    
    const pimUrl = 'https://url-of-your-pim.com';
    const accessToken = 'your_app_token'; // Token provided during oAuth steps
    
    // Set your client for querying Akeneo API as follows
    async function get(url, accessToken) {
        return await fetch(url, {
            headers: {
                'Authorization': `Bearer ${accessToken}`
            }
        });
    }
    

#Use case 1: Collect product variation information - all levels

#1. Collect product models

#1.1 You are following the App workflow?

In the guided tutorial How to get families, family variants, and attributes, we have stored a family_code_list. It’s time to use it!


    $client = buildApiClient();
    
    $maxProductsPerPage = 100;
    $maxFamiliesPerQuery = 10;
    $scope = 'ecommerce';
    
    // Get family codes from storage
    $familyCodes = getFamilyCodes();
    
    // Get locales from storage
    $locales = getLocales(); // ['en_US', 'fr_FR']
    
    $familyCodeChunks = array_chunk($familyCodes, $maxFamiliesPerQuery);
    
    $apiUrl = '/api/rest/v1/product-models?'
        . 'locales=%s'
        . '&scope=%s'
        . '&search={"family":[{"operator":"IN","value":%s}]}'
        . '&limit=%s';
    
    
    // Collect product models from API
    $productModels = [];
    foreach ($familyCodeChunks as $familyCodes) {
        $response = $client->get(
            sprintf(
                $apiUrl,
                implode(',', $locales),
                $scope,
                json_encode($familyCodes),
                $maxProductsPerPage
            )
        );
        $data = json_decode($response->getBody()->getContents(), true);
        $productModels[] = $data['_embedded']['items'];
    }
    
    $productModels = array_merge(...$productModels);
    
    // Save product models into storage
    storeProductModels($productModels);
    

    const maxProductsPerPage = 100;
    const maxFamiliesPerQuery = 10;
    const scope = 'ecommerce';
    
    // Get family codes from storage
    const familyCodes = await getFamilyCodes();
    // Get locales from storage
    const locales = await getlocales(); // ['en_US', 'fr_FR']
    
    // split familyCodes in chucks of $maxFamiliesPerQuery elements
    const chunks = [];
    while (familyCodes.length > 0) {
        chunks.push(familyCodes.splice(0, maxFamiliesPerQuery));
    }
    
    const productModels = [];
    for (const chunk of chunks) {
        const response = await get(`${pimUrl}/`
            + `api/rest/v1/product-models?`
            + `&locales=${locales.join(',')}`
            + `&scope=${scope}`
            + `&search={"family":[{"operator":"IN","value":${JSON.stringify(chunk)}}]}`
            + `&limit=${maxProductsPerPage}`,
            accessToken);
        const data = await response.json();
        const newProductModels = data['_embedded']['items'];
        productModels.push(...newProductModels);
    }
    
    // Save product models into storage
    storeProductModels(productModels);
    
#1.2 You are not following the App workflow?

Simply get the attribute type by requesting the API


    $client = buildApiClient();
    
    $maxProductsPerPage = 100;
    $scope = 'ecommerce';
    
    $nextUrl = sprintf(
        '/api/rest/v1/product-models?'
        . '&scope=%s'
        . '&limit=%s',
        $scope,
        $maxProductsPerPage,
    );
    
    $productModels = [];
    do {
        // Collect product models from API
        $response = $client->get($nextUrl);
        $data = json_decode($response->getBody()->getContents(), true);
        $productModels[] = $data['_embedded']['items'];
    
        $nextUrl = $data['_links']['next']['href'] ?? null;
    } while (
        $nextUrl
    );
    
    $productModels = array_merge(...$productModels);
    
    // Save product models into storage
    storeProductModels($productModels);
    

    const maxProductsPerPage = 100;
    const scope = 'ecommerce';
    
    let nextUrl = `${pimUrl}/api/rest/v1/product-models?`
        + `&scope=${scope}`
        + `&limit=${maxProductsPerPage}`;
    
    const productModels = [];
    
    do {
        // Collect product models from paginated API
        const response = await get(nextUrl, accessToken);
        const data = await response.json();
        const newProductModels = data['_embedded']['items'];
        productModels.push(...newProductModels);
    
        nextUrl = data._links?.next?.href;
    } while (nextUrl)
    
    // Save product models into storage
    storeProductModels(productModels);
    

Example output:


    var_export($productModels);
    
    // Output
    [
        [
            "_links" => [...],
            "code" => "Acme Classic Mens Black PVC Work Boots",
            "family" => "rubber_boots",
            "family_variant" => "rubber_boots_by_size",
            "parent" => null,
            "categories" => [
                "acme",
                "master_clothing_footwear_footwear_rubber_boots"
            ],
            "values" => [...],
            "created" => "2022-10-20T12:46:42+00:00",
            "updated" => "2022-10-20T13:14:04+00:00",
            "associations" => [...],
            "quantified_associations" => [],
            "metadata" => [
                "workflow_status" => "working_copy"
            ],
        ],
        /* ... */
    ]; 
    

    console.log(productModels);
    
    // Output
    [
        {
            "_links":{...},
            "code":"Acme Classic Mens Black PVC Work Boots",
            "family":"rubber_boots",
            "family_variant":"rubber_boots_by_size",
            "parent":null,
            "categories":[
                "acme",
                "master_clothing_footwear_footwear_rubber_boots"
            ],
            "values":{...},
            "created":"2022-10-20T12:46:42+00:00",
            "updated":"2022-10-20T13:14:04+00:00",
            "associations":{...},
            "quantified_associations":[],
            "metadata":{
                "workflow_status":"working_copy"
            }
        },
        /* ... */
    ]
    

#2. Process product model

#2.1. Parse and store the product model

Parse and store a product or a product model is definitely the same thing. Please have a look to our guided tutorial How to get families, family variants, and attributes.

#2.2. Collect its family variant
#2.2.1 You are following the App workflow?

Good news: you already store the family variant in the guided tutorial How to get families, family variants, and attributes. Go ahead!

#2.2.2 You are not following the App workflow?

Query the API.


    $client = buildApiClient();
    
    $maxProductsPerPage = 100;
    $apiUrl = '/api/rest/v1/families/%s/variants?limit=%s';
    
    // Get family codes from storage
    $codes = getFamilyCodes();
    
    // Collect family variants from API
    $familyVariants = [];
    foreach ($codes as $code) {
        $nextUrl = sprintf($apiUrl, $code, $maxProductsPerPage);
        do {
            // Collect family variants from API
            $response = $client->get($nextUrl);
            $data = json_decode($response->getBody()->getContents(), true);
            $familyVariants[] = $data['_embedded']['items'];
    
            $nextUrl = $data['_links']['next']['href'] ?? null;
        } while (
            $nextUrl
        );
    }
    
    $familyVariants = array_merge(...$familyVariants);
    
    // add index to $familyVariants
    $indexedFamilyVariants = [];
    foreach ($familyVariants as $familyVariant) {
        $indexedFamilyVariants[$familyVariant['code']] = $familyVariant;
    }
    
    // Save family variants into storage
    storeFamilyVariants($indexedFamilyVariants);
    

    const maxProductsPerPage = 100;
    
    // Get family codes from storage
    const familyCodes = await getFamilyCodes();
    
    let familyVariants = [];
    for (const code of familyCodes) {
        let nextUrl = `${pimUrl}/api/rest/v1/families/${code}/variants?limit=` + maxProductsPerPage;
        do {
            // Collect family variants from API
            const response = await get(nextUrl, accessToken);
            const data = await response.json();
            const newVariants = data['_embedded']['items'];
            familyVariants.push(...newVariants);
    
            nextUrl = data._links?.next?.href;
        } while (nextUrl)
    }
    
    // add index to familyVariants
    let indexedFamilyVariants = {};
    for (const familyVariant of familyVariants) {
        indexedFamilyVariants[familyVariant['code']] = familyVariant;
    }
    
    // Save family variants into storage
    storeFamilyVariants(indexedFamilyVariants);
    

Example output:


    var_export($indexedFamilyVariants);
    
    // Output
    [
        "beanies_by_color" => [
            "_links" => [...]
            "code" => "beanies_by_color",
            "labels" => [
                "en_US" => "Beanies by Color",
                "en_GB" => "Beanies by Color",
                "fr_FR" => "Beanies by Color",
                "de_DE" => "Beanies by Color",
                "ja_JP" => "\u8272\u5225\u30d3\u30fc\u30cb\u30fc"
            ],
            "variant_attribute_sets" => [
                [
                    "level" => 1,
                    "axes" => [
                        "color"
                    ],
                    "attributes" => [
                        "sku",
                        "erp_name",
                        "price",
                        "name",
                        "best_seller",
                        "clearance",
                        "inventory_level",
                        "badge",
                        "color",
                        "akeneo_onboarder_supplier",
                        "akeneo_onboarder_supplier_reference"
                    ],
                ],
            ],
        ],
        /* ... */
    ];
    
    

    console.log(indexedFamilyVariants);
    
    // Output
    {
        "beanies_by_color": {
            "_links": {...},
            "code": "beanies_by_color",
                "labels": {
                "en_US": "Beanies by Color",
                    "en_GB": "Beanies by Color",
                    "fr_FR": "Beanies by Color",
                    "de_DE": "Beanies by Color",
                    "ja_JP": "\u8272\u5225\u30d3\u30fc\u30cb\u30fc"
                },
            "variant_attribute_sets": [
                {
                    "level": 1,
                    "axes": [
                        "color"
                    ],
                    "attributes": [
                        "sku",
                        "erp_name",
                        "price",
                        "name",
                        "best_seller",
                        "clearance",
                        "inventory_level",
                        "badge",
                        "color",
                        "akeneo_onboarder_supplier",
                        "akeneo_onboarder_supplier_reference"
                    ]
                }
            ]
        },
        /* ... */
    }
    
    
#2.3. Collect its product variants

To get product variants associated to a product model, ask to the API


    $client = buildApiClient();
    
    $maxProductsPerPage = 100;
    $maxProductModelsPerQuery = 10;
    
    // Get product model codes from storage
    $productModelCodes = getProductModelCodes();
    // Get locales from storage
    $locales = getLocales(); // ['en_US', 'fr_FR']
    
    $productModelCodesChunks = array_chunk($productModelCodes, $maxProductModelsPerQuery);
    
    $apiUrl = '/api/rest/v1/products-uuid?'
        . 'locales=%s'
        . '&search={"parent":[{"operator":"IN","value":%s}]}'
        . '&limit=%s';
    
    // Collect product models from API
    $productVariants = [];
    foreach ($productModelCodesChunks as $productModelCodes) {
        $response = $client->get(sprintf($apiUrl, json_encode($productModelCodes), $maxProductsPerPage));
        $data = json_decode($response->getBody()->getContents(), true);
        $productVariants[] = $data['_embedded']['items'];
    }
    
    $productVariants = array_merge(...$productVariants);
    

    const maxProductsPerPage = 100;
    const maxProductModelsPerQuery = 10;
    
    // Get product model codes from storage
    const productModelCodes = await getProductModelCodes();
    // Get locales from storage
    const locales = await getlocales(); // ['en_US', 'fr_FR']
    
    // split productModelCodes in chucks of $maxFamiliesPerQuery elements
    const chunks = [];
    while (productModelCodes.length > 0) {
        chunks.push(productModelCodes.splice(0, maxProductModelsPerQuery));
    }
    
    const productVariants = [];
    for (const chunk of chunks) {
        const response = await get(`${pimUrl}/`
            + `api/rest/v1/products-uuid?`
            + `locales=${locales.join(',')}`
            + `&search={"parent":[{"operator":"IN","value":${JSON.stringify(chunk)}}]}`
            + `&limit=${maxProductsPerPage}`,
            accessToken);
        const data = await response.json();
        const newProductVariants = data['_embedded']['items'];
        productVariants.push(...newProductVariants);
    }
    

Example output:


    var_export($productVariants);
    
    // Output
    [
        [
            "_links" => [...],
            "uuid" => "02a1d432-8bb7-4b4b-8d28-8b8f8d0e7b9f",
            "enabled" => true,
            "family" => "patio_furniture_sets",
            "categories" => [
                "erp_patio_furniture_sets",
                "master_outdoors_patio_furniture_outdoor_lounge_furniture_patio_conversation_sets"
            ],
            "groups" => [],
            "parent" => "Becker 4-Piece Dark Mocha Steel Outdoor Patio Seating Set with CushionGuard Cushions",
            "values" => [
                "fabric_type" => [
                    ["locale" => null...]
                ],
                "chair_design" => [
                    ["locale" => null...]
                ],
                 "pattern_type" => [
                    ["locale" => null...]
                ],
                /* ... */
            ],
            "created" => "2022-08-11T09:20:19+00:00",
            "updated" => "2022-08-11T09:20:19+00:00",
            "associations" => [
                "PACK" => [...],
                "UPSELL" => [...],
                "X_SELL" => [...],
                "SUBSTITUTION" => [...]
            ],
            "quantified_associations" => [],
            "metadata" => [
                "workflow_status" => "working_copy"
            ]
        ], 
      /* ... */
    ]
    
    

    console.log(productVariants);
    
    // Output
    [
        {
            "_links": {...},
            "uuid": "02a1d432-8bb7-4b4b-8d28-8b8f8d0e7b9f",
            "enabled": true,
            "family": "patio_furniture_sets",
            "categories": [
                "erp_patio_furniture_sets",
                "master_outdoors_patio_furniture_outdoor_lounge_furniture_patio_conversation_sets"
            ],
            "groups": [],
            "parent": "Becker 4-Piece Dark Mocha Steel Outdoor Patio Seating Set with CushionGuard Cushions",
            "values": {
                "fabric_type": [
                    {"locale": null...}
                ],
                "chair_design": [
                    {"locale": null...}
                ],
                "pattern_type": [
                    {"locale": null...}
                ]
                /* ... */
            },
            "created": "2022-08-11T09:20:19+00:00",
            "updated": "2022-08-11T09:20:19+00:00",
            "associations": {
                "PACK": {...},
                "UPSELL": {...},
                "X_SELL": {...},
                "SUBSTITUTION": {...}
            },
            "quantified_associations": [],
            "metadata": {
                "workflow_status": "working_copy"
            }
        }
    ]
    
    

Again, treat each product like a simple product. Please refer to the guided tutorial How to get families, family variants, and attributes

#Use case 2: Collect product variation information - set it all on 1 level

This use case follows the same logic, but here you will set all the variations of a product on one level only.

#1. Collect product models

#1.1 You are following the App workflow?

In the guided tutorial How to get families, family variants, and attributes, we have stored a family_code_list. It’s time to use it!


    $client = buildApiClient();
    
    $maxProductsPerPage = 100;
    $maxFamiliesPerQuery = 10;
    $scope = 'ecommerce';
    
    // Get family codes from storage
    $familyCodes = getFamilyCodes();
    // Get locales from storage
    $locales = getLocales(); // ['en_US', 'fr_FR']
    
    $familyCodeChunks = array_chunk($familyCodes, $maxFamiliesPerQuery);
    
    $apiUrl = '/api/rest/v1/product-models?'
        . 'locales=%s'
        . '&scope=%s'
        . '&search={"family":[{"operator":"IN","value":%s}],"parent":[{"operator":"EMPTY"}]}'
        . '&limit=%s';
    
    
    // Collect product models from API
    $productModels = [];
    foreach ($familyCodeChunks as $familyCodes) {
        $response = $client->get(
            sprintf(
                $apiUrl,
                implode(',', $locales),
                $scope,
                json_encode($familyCodes),
                $maxProductsPerPage
            )
        );
        $data = json_decode($response->getBody()->getContents(), true);
        $productModels[] = $data['_embedded']['items'];
    }
    
    $productModels = array_merge(...$productModels);
    
    // Get family variants from storage
    $familyVariants = getFamilyVariants();
    foreach ($productModels as $key => $productModel) {
        $familyVariant = $familyVariants[$productModel['family_variant']];
        // extract all variations level
        $axes = array_column($familyVariant['variant_attribute_sets'], 'axes');
        // build flat axes
        $productModels[$key]['axes'] = array_merge(...$axes);
    }
    
    // Save product models into storage
    storeProductModels($productModels);
    

    const maxProductsPerPage = 100;
    const maxFamiliesPerQuery = 10;
    const scope = 'ecommerce';
    
    // Get family codes from storage
    const familyCodes = await getFamilyCodes();
    // Get locales from storage
    const locales = await getlocales(); // ['en_US', 'fr_FR']
    
    // split familyCodes in chucks of $maxFamiliesPerQuery elements
    const chunks = [];
    while (familyCodes.length > 0) {
        chunks.push(familyCodes.splice(0, maxFamiliesPerQuery));
    }
    
    const productModels = [];
    for (const chunk of chunks) {
        const response = await get(`${pimUrl}/`
            + `api/rest/v1/product-models?`
            + `&locales=${locales.join(',')}`
            + `&scope=${scope}`
            + `&search={"family":[{"operator":"IN","value":${JSON.stringify(chunk)}}],"parent":[{"operator":"EMPTY"}]}`
            + `&limit=${maxProductsPerPage}`,
            accessToken);
        const data = await response.json();
        const newProductModels = data['_embedded']['items'];
        productModels.push(...newProductModels);
    }
    
    // Get variants from storage
    const variants = await getFamilyVariants();
    let productModelsWithAxes = [];
    for (const productModel of productModels) {
        for (const [code, variant] of Object.entries(variants)) {
            if (productModel['family_variant'] === code) {
                // extract all variations level
                productModelsWithAxes.push(
                    {...productModel, 'axes': variant['variant_attribute_sets'].map((value) => value.axes).flat()}
                );
            }
        }
    }
    
    // Save product models into storage
    storeProductModels(productModelsWithAxes);
    
#1.2 - You are not following the App workflow?

Make sure to get the list of your family variants before continuing like in How to get families, family variants, and attributes


    $client = buildApiClient();
    
    $maxProductsPerPage = 100;
    $scope = 'ecommerce';
    
    $nextUrl = sprintf(
        '/api/rest/v1/product-models?'
        . '&scope=%s'
        . '&search={"parent":[{"operator":"EMPTY"}]}'
        . '&limit=%s',
        $scope,
        $maxProductsPerPage,
    );
    
    // Collect product models from API
    $productModels = [];
    do {
        // Collect product models from API
        $response = $client->get($nextUrl);
        $data = json_decode($response->getBody()->getContents(), true);
        $productModels[] = $data['_embedded']['items'];
    
        $nextUrl = $data['_links']['next']['href'] ?? null;
    } while (
        $nextUrl
    );
    
    $productModels = array_merge(...$productModels);
    
    // Get family variants from storage
    $familyVariants = getFamilyVariants();
    foreach ($productModels as $key => $productModel) {
        $familyVariant = $familyVariants[$productModel['family_variant']];
        // extract all variations level
        $axes = array_column($familyVariant['variant_attribute_sets'], 'axes');
        // build flat axes
        $productModels[$key]['axes'] = array_merge(...$axes);
    }
    
    // Save product models into storage
    storeProductModels($productModels);
    

    const maxProductsPerPage = 100;
    const scope = 'ecommerce';
    
    let nextUrl = `${pimUrl}/api/rest/v1/product-models?`
        + `&scope=${scope}`
        + `&search={"parent":[{"operator":"EMPTY"}]}`
        + `&limit=${maxProductsPerPage}`;
    
    const productModels = [];
    
    do {
        // Collect product models from paginated API
        const response = await get(nextUrl, accessToken);
        const data = await response.json();
        const newProductModels = data['_embedded']['items'];
        productModels.push(...newProductModels);
    
        nextUrl = data._links?.next?.href;
    } while (nextUrl)
    
    // Get variants from storage
    const variants = await getFamilyVariants();
    let productModelsWithAxes = [];
    for (const productModel of productModels) {
        for (const [code, variant] of Object.entries(variants)) {
            if (productModel['family_variant'] === code) {
                // extract all variations level
                productModelsWithAxes.push({...productModel, 'axes': variant['variant_attribute_sets'].map((value) => value.axes).flat()});
            }
        }
    }
    
    // Save product models into storage
    storeProductModels(productModelsWithAxes);
    

Example output:


    var_export($productModels);
    
    // Output
    [
        [
            "_links" => [...],
            "code" => "Acme Classic Mens Black PVC Work Boots",
            "family" => "rubber_boots",
            "family_variant" => "rubber_boots_by_size",
            "parent" => null,
            "categories" => [
                "acme",
                "master_clothing_footwear_footwear_rubber_boots"
            ],
            "values" => [...],
            "created" => "2022-10-20T12:46:42+00:00",
            "updated" => "2022-10-20T13:14:04+00:00",
            "associations" => [...],
            "quantified_associations" => [],
            "metadata" => [
                "workflow_status" => "working_copy"
            ],
            "axes" => ["shoe_size"]
        ],
        /* ... */
    ]; 
    

    console.log(productModels);
    
    // Output
    [
        {
            "_links":{...},
            "code":"Acme Classic Mens Black PVC Work Boots",
            "family":"rubber_boots",
            "family_variant":"rubber_boots_by_size",
            "parent":null,
            "categories":[
                "acme",
                "master_clothing_footwear_footwear_rubber_boots"
            ],
            "values":{...},
            "created":"2022-10-20T12:46:42+00:00",
            "updated":"2022-10-20T13:14:04+00:00",
            "associations":{...},
            "quantified_associations":[],
            "metadata":{
                "workflow_status":"working_copy"
            },
            "axes":["shoe_size"]
        },
        /* ... */
    ]
    

#2. Process product model

#2.1. Parse and store the product model

Parse and store the product model like in 2.1. Parse and store the product model

#2.2. Collect its product variants

Collect product variants the same way than in 2.2. Collect its product variants

The variations should be on one level now.

Next Step
Well done! Keep digging into the “App workflow” and follow the next tutorial!