#How to get families, family variants, and attributes

Use case:
App Workflow
PIM Features:
Attributes
Families
REST API endpoint(s):
family family variants attributes
If you're starting to build your App, make sure you previously followed:

#Context

Families and attributes are the basis of an Akeneo catalog structure: get them before retrieving the products from the PIM.

If you plan to get product variants and their corresponding models, we advise you to retrieve now the associated family variants.

relationship schema

Get the big picture here.

#Fetch the catalog structure: families and attributes

#Workflow

relationship schema

#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
    
        // Set your client for querying Akeneo API as follows
        $client = 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}`
            }
        });
    }
    

#1 - Collect families and attribute codes

Get families and attribute codes by requesting the PIM API


    $client = buildApiClient();
    
    const MAX_ITEMS = 100;
    
    $nextUrl = '/api/rest/v1/families?search={"has_products":[{"operator":"=","value":true}]}&limit=' . MAX_ITEMS;
    
    $families = $attributeCodes = [];
    do {
        // Collect families and list of unique attribute codes from API
        $response = $client->get($nextUrl);
        $data = json_decode($response->getBody()->getContents(), true);
        $families[] = $data['_embedded']['items'];
        $attributeCodes = array_merge(
            $attributeCodes,
            ...array_column($data['_embedded']['items'], 'attributes'));
    
        $nextUrl = $data['_links']['next']['href'] ?? null;
    } while (
        $nextUrl
    );
    
    $families = array_merge(...$families);
    
    $attributeCodes = array_unique($attributeCodes);
    
    // Save families and attribute codes into storage
    storeFamilies($families);
    storeAttributesCodes($attributeCodes);
    

    const maxItems = 100;
    let nextUrl = `${pimUrl}/api/rest/v1/families?search={"has_products":[{"operator":"=","value":true}]}&limit=${maxItems}`;
    
    const families = [];
    do {
        const response = await get(nextUrl, accessToken);
        const data = await response.json();
        const newFamilies = data['_embedded']['items']
        families.push(...newFamilies);
    
        nextUrl = data._links?.next?.href;
    } while (nextUrl)
    
    // Collect attributes from all families
    const attributeCodes = families.reduce(
        (acc, family) => [...acc, ...family.attributes],
        []
    );
    
    const uniqueAttributes = [...new Set(attributeCodes)];
    
    // Save families and attribute codes into storage
    storeFamilies(families);
    storeAttributeCodes(uniqueAttributes);
    

Store family codes in a family_code_list and attribute codes in a separate list (attribute_code_list). We will deal with attribute_code_list later in this tutorial.

Warning! with the API call GET api/rest/v1/families, you will collect all the families into the database! Please ask yourself this question before continuing: Do I really need all of them? At this step, it’s the perfect occasion to save time later, during products synchronization. We strongly advise you to filter your families as much as you can before building family_code_list and attribute_code_list.
👉 One way to do this is the family codes filter

#2 - Collect family variants

This step is mandatory if you want to synchronize product variants later. If not, jump to the third step.

Get family variants by requesting the PIM API for each families


    $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);
    

#3 - Collect attributes

Remember your attribute_code_list? It’s (already) time to use it to retrieve attribute information


    $client = buildApiClient();
    
    const MAX_ITEMS = 100;
    const API_URL = '/api/rest/v1/attributes?search={"code":[{"operator":"IN","value":%s}]}&limit=%s';
    
    // Get attributes codes from storage
    $attributeCodes = getAttributesCodes();
    
    // Collect attributes from API
    $rawAttributes = [];
    foreach (array_chunk($attributeCodes, MAX_ITEMS) as $chunk) {
        $response = $client->get(sprintf(API_URL, json_encode($chunk), MAX_ITEMS));
        $data = json_decode($response->getBody()->getContents(), true);
        $rawAttributes[] = $data['_embedded']['items'];
    }
    
    $rawAttributes = array_merge(...$rawAttributes);
    
    // Only keep fields needed
    $attributes = [];
    foreach ($rawAttributes as $rawAttribute) {
        $attributes[$rawAttribute['code']] = [
            'code' => $rawAttribute['code'],
            'type' => $rawAttribute['type'],
            // Add additional fields if needed
        ];
    }
    
    // save attributes into storage
    storeAttributes($attributes);
    

    const maxItems = 100;
    
    // Get attributes codes from storage
    const attributeCodes = await getAttributeCodes();
    const maxAttributesPerQuery = 10;
    
    // split attributeCodes in chucks of $maxAttributesPerQuery elements
    let chunks = [];
    while (attributeCodes.length > 0) {
        chunks.push(attributeCodes.splice(0, maxAttributesPerQuery));
    }
    
    let rawAttributes = [];
    for (const item of chunks) {
        let nextUrl = `${pimUrl}/api/rest/v1/attributes?search={"code":[{"operator":"IN","value":${JSON.stringify(item)}}]}&limit=` + maxItems;
        do {
            const response = await get(nextUrl, accessToken);
            let data = await response.json();
            let newRawAttributes = data['_embedded']['items']
            rawAttributes.push(...newRawAttributes);
            nextUrl = data._links?.next?.href;
        } while (nextUrl);
    }
    
    // Only keep fields needed
    let attributes = {};
    for (const rawAttribute of rawAttributes) {
        attributes[rawAttribute['code']] = {
            'code': rawAttribute['code'],
            'type': rawAttribute['type'],
            // Add additional fields if needed
        };
    }
    
    //save attributes into storage
    storeAttributes(attributes);
    

Retrieved attribute list follows this structure:


    // Output
    [
        'attribute_code' => [
                'code' => 'attribute_code',
                'type' => 'pim_catalog_text',
        ],
    ]
    

    // Output
    {
    	"attribute_code":
    	{
    		"code": "attribute_code",
    		"type": "pim_catalog_text"
    	}
    }
    

attribute_code_list may be significant, very big! If you get an HTTP 414 error , you probably hit these boundaries. A workaround is to split your attribute_code_list into different parts and call them independently.

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