Getting Started With SCAYLE
Create Shop Categories
- Getting started
- Tech
Robert Merten
VP Tech
Shop Categories help you to cluster your Assortment in multiple Categories and render your Categories as a Tree.
There is a m:n relation between Products & Shop Categories. So each Category can have multiple Products and each Product can be assigned to multiple Categories.
Each category comes with a name and a path.
The category name is a localised string:
[
'de_DE' => 'Kleider',
'en_EN' => 'Dresses',
]
The category path gets automatically calculated by SCAYLE and represents a unique category identifier which can be used for lookups.
It contains the full path (including all parent category names) and looks like this:
/women/clothing/dresses
Each Shop Category comes with two attribute values defining their Status & Visibility in the Storefront:
isVisible
- this attribute defines if the Storefront should render the Category to the CustomerisActive
- this attribute defines whether the Shop Category is active or inactiveisExcludedFromSearch
- This Attribute defines whether the Shop Category should be excluded from the search.The Shop Category Status can be managed by the Shop Managers in the SCAYLE Panel.
If you're set a Shop Category to 'inactive', all child-categories automatically inherit from this action and also will be set to 'invisible' or 'deactivated'.
In case you activate a child-category all parent-categories will get activated within the same action.
If you're set a Shop Category to invisible there is no inheritance logic implemented.
SCAYLE offers multiple ways to map the products to the corresponding shop categories:
Be aware of the following limitation:
If you use include or exclude Products by their ID's you're not able to add other criteria but only can reference product sets of the same type.
You can define the supported filter which can be used on a category page to filter products & do faceting.
SCAYLE lets you define additional data on your Shop Category via Shop Category Properties.
This is useful for multiple reasons, just to highlight some:
Shop Category Properties allow you to store additional data to pre-defined keys.
Both - the key and the value must be a string.
The definition of the Shop Category Property Key is done on SCAYLE instance level.
You can use the defined key on every shop within your instance.
let shopCategoryPropertyKey = {
"key": "sale",
"isInheritable": true
};
let response = await adminApi.apis.ShopCategories.createShopCategoryPropertyKey({requestBody: shopCategoryPropertyKey});
let createdShopCategoryPropertyKey = response.body;
Shop Category Properties can be inherited from a parent category to its child categories by setting the isInheritable
flag to true
.
Once you created a shop category property key, you can come back and update it later. However, you can only modify a key if it is currently not used within a shop category.
let shopCategoryPropertyKey = {
key: "new",
isInheritable: true
};
let response = await client.apis.ShopCategories.updateShopCategoryPropertyKey(
{shopCategoryPropertyKey: "sale"},
{requestBody: shopCategoryPropertyKey}
);
let updatedShopCategoryPropertyKey = response.body;
You can request a shop category property keys by simply providing its name.
let response = await client.apis.ShopCategories.getShopCategoryPropertyKey({shopCategoryPropertyKey: "sale"});
let shopCategoryPropertyKey = response.body;
If you would like to retrieve more than one shop category property key, you can fetch them all at once.
let response = await client.apis.ShopCategories.getShopCategoryPropertyKeys();
let shopCategoryPropertyKeys = response.body.entities;
After you created a Shop Category Property Key you can set the property value for a specific Shop Category.
In the example below we set the value 'NoSale' for the property key 'sale' for the Shop Category with the ID 1 within the Shop with the key 'ms' and the country code 'DE' (Germany):
let shopCategoryProperty = {
"key": "sale",
"value": "NoSale"
};
let response = await adminApi.apis.ShopCategories.updateOrCreateShopCategoryProperty({shopKey: "ms", countryCode: "DE", shopCategoryId: 1}, {requestBody: shopCategoryProperty});
let updatedShopCategoryProperty = response.body;
If you want to update a shop property or add it - in case it doesn't exist - you can use the updateOrCreateProperty method:
let shopCategoryProperty = {
"key": "sale",
"value": "NoSale"
};
let response = await adminApi.apis.ShopCategories.updateOrCreateShopCategoryProperty({shopKey: "ms", countryCode: "DE", shopCategoryId: 1}, {requestBody: shopCategoryProperty});
let updatedShopCategoryProperty = response.body;
In case you want to remove the value for a saved Shop Category Property for a specific key you can use the deleteProperty method:
$adminApi->shopCategories->deleteProperty('ms', 'DE', 1, 'sale');
If you want to request a shop category property, you need to provide its respective category ID and property key.
let response = await client.apis.ShopCategories.getShopCategoryProperty({shopKey: "ms", countryCode: "DE", shopCategoryId: 1, shopCategoryPropertyKey: "sale"});
let shopCategoryProperty = response.body;
You can retrieve all shop category properties for a specific shop category. In this case you don't pass any property key to the call:
$shopCategoryPropertyCollection = $adminApi->shopCategories->allProperties('ms', 'DE', 1);
If you work with multiple Shop Countries (e.g. Shop.com with a dutch and a german shop country) you normally manage the category tree only once.
SCAYLE offers you multiple features to make the shop category handling in such a scenario very efficient.
The general shop category tree is shared across all shops, but you can change the following settings on shop country level:
If you still want to have a dedicated shop category tree for a shop country you have to set a new categoryTreeId
when you create the shop country, as documented here.
Shop Categories represent a nested category tree. Each node holds it's own product set. So the parent category node does not contain the products of it's children by default. If you want to enable this behavior you need to make sure that a parent category node contains all product set configurations of it's children.
To manage Shop Categories you need to work with the Admin API.
The most minimal way to create a new shop category for the shop with the shop key 'ms' looks like this:
let shopCategory = {
"name": {
"en_EN": "New shop category"
},
"isActive": true,
"isVisible": true,
"isExcludedFromSearch": false
};
let response = await adminApi.apis.ShopCategories.createShopCategory({shopKey: "ms"}, {requestBody: shopCategory});
let createdShopCategory = response.body;
countries.path
field might not be present in the response.isActive
, isVisible
, and properties
will be propagated to all existing shop countries if specified.Parameter | Details |
---|---|
id | Integer READ-ONLY The ID of the shop category. |
parentId | Integer The ID of the parent shop category. |
leftSiblingId | Integer The ID of the left sibling shop category. It defines the shop category position in the category tree. |
name | String The localized category name. |
productSets | ShopCategoryProductSet Product sets define which products to include in the shop category. |
supportedFilterGroups | String List of supported filter groups. |
properties | ShopCategoryProperty The properties assigned to the shop category. Can be specified on creation only. |
isActive | Boolean Declares whether the shop category is active or not. Can be specified on creation only. |
isVisible | Boolean Declares whether the shop category is visible in the shop or not. Can be specified on creation only. |
customData | CustomData Arbitrary fields assigned to shop categories |
countries | ShopCategoryCountry READ-ONLY List of country specific configurations. |
Property | Type | Description |
---|---|---|
countryCode | String READ-ONLY | ISO 3166 alpha 2 country code |
shopCountryId | Integer READ-ONLY | Id of Shop Country |
path | String READ-ONLY | String representation of the URL path to the category |
isActive | Boolean | Declares whether the shop category is active or not |
isVisible | Boolean | Declares whether the shop category is visible in the shop or not |
properties | ShopCategoryProperty[] | The properties assigned to the shop category |
customData | CustomData | Arbitrary fields assigned to shop category countries |
Property | Type | Description |
---|---|---|
key | String | The key of the shop category property |
value | String | The value of the shop category property |
When creating a shop category, it is important to keep the following details in mind:
leftSiblingId
is only used to determine a new category position. The category will be inserted after the given left sibling category.countries.path
field might not be present in the response.isActive
, isVisible
, and properties
will be propagated to all existing shop countries if specified. It is important to note that new shop countries will not automatically receive these values. You have to create a new configuration each time a shop country is created.isActive
flag to false
will also deactivate its child categories. Activating a child category will activate all respective parent categories.sale
. It allows you to include or exclude discounted products in the assortment by setting the value respectively to Sale
or NoSale
.name
has a translation that matches the country's language, then this value is used as a category name. Otherwise, the default language translation is used as a country specific name.If there exists a custom data configuration for the shopCategory
entity and the custom data isn't provided within the create request, the defaultValue
specified in the custom data configuration for each property would be considered.
It's possible to directly add some shop properties to a shop category create call:
let shopCategory = {
"name": {
"de_DE": "New Shop Category"
},
"isActive": true,
"isVisible": true,
"properties": [
{
"key": "sale",
"value": "NoSale"
}
]
};
let response = await adminApi.apis.ShopCategories.createShopCategory({shopKey: "ms"}, {requestBody: shopCategory});
let createdShopCategory = response.body;
The attribute 'parentId'
defines the parent category id.
You can create a category 'Apparel' and a child-category 'Dresses' with the following code:
let apparelShopCategory = {
"name": {
"en_EN": "Apparel"
},
"isActive": true,
"isVisible": true
};
let response = await adminApi.apis.ShopCategories.createShopCategory({shopKey: "<shop-key>"}, {requestBody: apparelShopCategory});
let createdApparelCat = response.body;
let dressesShopCategory = {
"name": {
"de_DE": "New Shop Category"
},
"isActive": true,
"isVisible": true,
"parentId": createdApparelCat.id
};
let response = await adminApi.apis.ShopCategories.createShopCategory({shopKey: "<shop-key>"}, {requestBody: dressesShopCategory});
let createdDressesCat = response.body;
In case you have multiple shop categories on the same hierarchy level you typically want to define the sorting of the category nodes.
This can be done by using the 'leftSiblingId
' property. The new Category 'Dresses' will be inserted after the given left sibling category (in the example below category with ID 100).
let shopCategory = {
"name": {
"en_EN": "Dresses"
},
"isActive": true,
"isVisible": true,
"leftSiblingId": 100
};
let response = await adminApi.apis.ShopCategories.createShopCategory({shopKey: "<shop-key>"}, {requestBody: shopCategory});
let createdDressesCat = response.body;
let shopCategory = {
"parentId": 1,
"leftSiblingId": 2,
"name": {
"en_EN": "Red Products Category"
},
"productSets": [
{
"isNew": true,
"attributes": [
{
"name": "color",
"include": ["red"]
}
]
},
{
"referencedProductSetId": 1,
"includeProductIds": [1, 2]
},
{
"excludeProductIds": [4]
}
],
"isActive": true,
"isVisible": true,
"properties": [
{
"key": "sale",
"value": "NoSale"
}
]
};
let response = await adminApi.apis.ShopCategories.createShopCategory({shopKey: "ms"}, {requestBody: shopCategory});
let createdShopCategory = response.body;
To update a shop category SCAYLE offers a update method:
let shopCategory = {
"name": {
"en_EN": "Sneaker"
}
};
let response = await adminApi.apis.ShopCategories.updateShopCategory({shopKey: "ms", shopCategoryId: 1}, {requestBody: shopCategory});
let updatedShopCategory = response.body;
The category path is automatically recalculated when a category name or its parent are changed. This operation is asynchronous and can be executed with a delay. This is the reason why the countries.path
field might be not present in the response for a short period of time.
If a shop category was initially created with a product set that references another product set via the 'referencedProductSetId
' attribute and this reference is not sent again in the update request, the reference is removed.
Please note that `productSets.includeProductIds` and `productSets.excludeProductIds` are both limited to a maximum of 10,000 Product IDs.
You can get a shop category and select the additional information & data which should be returned for the selected shop category.
The following options are available for the 'with' paramter:
let response = await adminApi.apis.ShopCategories.getShopCategory({shopKey: "ms", shopCategoryId: 1, with: "countries"});
let shopCategory = response.body;
To fetch multiple shop categories SCAYLE offers you pagination and filtering.
let response = await adminApi.apis.ShopCategories.getShopCategories({shopKey: "shopKey"});
let shopCategoryCollection = response.body.entities;
Parameter | Type | Details |
---|---|---|
with | String | Allows to load the following nested resources within this request:
|
limit | Integer | Maximum number of items in the result. (default: 100 , maximum: 1000 ) |
filters[id] | String | Comma-separated list of shop category IDs that should be used for filtering. |
filters[minId] | Integer | Minimum ID of shop categories, which should be returned. |
filters[maxId] | Integer | Maximum ID of shop categories, which should be returned. |
filters[parentId] | Integer | Filter shop categories by parent ID. |
You can delete a shop category. In this case all children also will get deleted.
adminApi.apis.ShopCategories.deleteShopCategory({shopKey: shopKey, shopCategoryId: shopCategoryId});
Direct and indirect incoming references (if any) will be deleted without copying conditions over.
This method can be used to unlink a product set from the referencing product sets. It is possible to copy conditions of the reference to the product sets from which it will be unlinked.
client.apis.ShopCategories.unlinkReferencingProductSets({shopKey: shopKey, productSetId: productSetId}, {requestBody: productSetUnlinkInstruction});
Parameter | Details |
---|---|
productSetIds | Integer IDs of the referencing product sets for which the reference to the stated product set should be unlinked. |
copyConditions | Boolean Declares whether the conditions of the stated product set should be copied over to the referencing product sets. |
If you want to unlink a product set but preserve its conditions (e.g. excluded or included product ids), you need to set copyConditions
= true. In this case, all conditions defined for the unlinked reference product set will be copied to the product set IDs provided in the payload.
let productSetUnlinkInstruction = {
productSetIds: [10, 22],
copyConditions: true
};
client.apis.ShopCategories.unlinkReferencingProductSets({shopKey: "ms", productSetId: 1}, {requestBody: productSetUnlinkInstruction});
To fetch the Shop Categories in your Storefront you need to work with the Storefront API.
If you are not working with a specific child category yet, root categories are used to access the whole category tree. Usually the category tree makes a fundamental part of the navigation in an online shop.
Furthermore, there are certain parameters (such as name, slug, path, and description) that are used by default when requesting a category.
Categories are related by parentId
and childrenIds
.
Root-level categories have a parentId = 0
We can use a shop category tree for the navigation in our shop.
// Get full category tree
const categories = await client.categories.getRoots({
with: { children: 1 },
});
for (const category of categories) {
console.log(category.name)
}
By default each category comes with its childrenIds
. But to iterate through our category tree and to build a link structure we need to ask for the fields of the children categories, e.g. name
, slug
or even their children
. To do so, we pass children
as our with
parameter.
If you want to limit the number of levels to receive you can provide a numeric depth
parameter.
Retrieve a shop category by ID or path with parent and child categories.
Categories are stored in a tree structure. Using this API, any individual category can be requested. Additionally, it is possible to include its parents (recursively) and children.
You can query a single category by specifying its ID or path. A path consists of the category's slug prepended with the slugs of its parent up to the root category.
We know the category ID of 'Women' is 6. To simply get the category's own properties we just have to pass the ID as parameter.
const category = await client.categories.getById(6, {});
console.log(category.name)
In this example we query the category "Shirts", id 8, and also all its parent categories which are "Women" and "Clothing".
const category = await client.categories.getById(6, {
with: { parents: 'all' },
});
console.log(category.name)
console.log(category.parent?.name)
In the previous example we saw that our category "Women" has a field childrenIds
. Of course we could now look up each of these categories with a dedicated requests. We could also request them all by their IDs in a single second request, see this example. But instead we are going to safe these extra requests and include all children by specifying them as with parameter.
const category = await client.categories.getById(6, {
with: { children: 1 },
});
console.log(category.name)
console.log(category.children?.length)
You can also query a category by its path instead of its ID.
const category = await client.categories.getByPath(
["women", "clothing", "shirts"],
);
console.log(category.name);
It's possible to retrieve multiple shop categories by their IDs. Useful when you have a single category page and want to tease and link to it's children.
This time we are on one of our root categories, "Women". We already know their childrenId
s from our previous data request in this example.
Now we want to know more about our children categories. So we pass their ids as a parameter. Also we set the depth to get the properties of their children as well.
// Get properties of multiple categories plus their children
const childrenIds = [7, 9, 10];
const categories = await client.categories.getByIds(childrenIds, {
with: {
absoluteDepth: 2,
},
});
console.log(categories[0].name);
console.log(categories[0].children[0].name);
// => Clothing
// => Shirts
absoluteDepth
and depth
start from the category itself, so 1
will retrieve only the categories with specified IDs, 2
will retrieve their first children etc.
Sometimes we also want to get information about the parents of our categories.
In this example we request information about two shirt categories.
They are similar and even belong to parent categories with the same name. But they are different. One is a child of Men
, the other one a child of Women
.
To get the parent we pass it in the with
parameter.
// Get properties of multiple categories plus their parents
const childrenIds = [3, 8];
const categories = await client.categories.getByIds(childrenIds, {
with: {
parents: "all",
},
});
console.log(categories[0].parent.parent.name);
console.log(categories[1].parent.parent.name);
// => Men
// => Women
[
{
"id": 3,
"path": "/men/clothing/shirts",
"name": "Shirts",
"slug": "shirts",
"description": "",
"parentId": 2,
"rootlineIds": [1, 2, 3],
"childrenIds": [],
"properties": [],
"isHidden": false,
"depth": 3,
"supportedFilter": [],
"parent": {
"id": 2,
"path": "/men/clothing",
"name": "Clothing",
"slug": "clothing",
"description": "",
"parentId": 1,
"rootlineIds": [1, 2],
"childrenIds": [3],
"properties": [],
"isHidden": false,
"depth": 2,
"supportedFilter": [],
"parent": {
"id": 1,
"path": "/men",
"name": "Men",
"slug": "men",
"description": "",
"parentId": 0,
"rootlineIds": [1],
"childrenIds": [2, 4, 5],
"properties": [],
"isHidden": false,
"depth": 1,
"supportedFilter": [],
"shopLevelCustomData": {},
"countryLevelCustomData": {}
},
"shopLevelCustomData": {},
"countryLevelCustomData": {}
},
"shopLevelCustomData": {},
"countryLevelCustomData": {}
},
{
"id": 8,
"path": "/women/clothing/shirts",
"name": "Shirts",
"slug": "shirts",
"description": "",
"parentId": 7,
"rootlineIds": [6, 7, 8],
"childrenIds": [],
"properties": [],
"isHidden": false,
"depth": 3,
"supportedFilter": [],
"parent": {
"id": 7,
"path": "/women/clothing",
"name": "Clothing",
"slug": "clothing",
"description": "",
"parentId": 6,
"rootlineIds": [6, 7],
"childrenIds": [8],
"properties": [],
"isHidden": false,
"depth": 2,
"supportedFilter": [],
"parent": {
"id": 6,
"path": "/women",
"name": "Women",
"slug": "women",
"description": "",
"parentId": 0,
"rootlineIds": [6],
"childrenIds": [7, 9, 10],
"properties": [],
"isHidden": false,
"depth": 1,
"supportedFilter": [],
"shopLevelCustomData": {},
"countryLevelCustomData": {}
},
"shopLevelCustomData": {},
"countryLevelCustomData": {}
},
"shopLevelCustomData": {},
"countryLevelCustomData": {}
}
]
This method does not allow inclusion of hidden child categories. If you need to retrieve those, have a look at the Get a Single Shop Category.
You can retrieve the products count for all categories using our Filters Endpoint. Categories which don't have any product assigned to them will not be returned.
You are also able to pass filters to the endpoint to be able to check which categories have products in combination with the filters.
In this example, all categories with products having a maximum price of 10 euros are returned.
const categories = await client.filters.getValues('categoryIds', {
where: {
maxPrice: 1000,
},
});
for (const category of categories) {
console.log(category)
}
[
{
"id": 1,
"productCount": 10
},
{
"id": 2,
"productCount": 20
},
]
Getting Started With SCAYLE
Robert Merten
VP Tech