Working with attributes
Last updatedCentra supports adding custom attributes to many types of objects, like Product, Display, Category, Customer, Order, Voucher, etc. You can see the full list here.
Defining attribute types#
Before attributes can be added to objects, they have to be defined in the Configuration section of Centra Admin Panel. Click here to find more details about custom attributes configuration.
Before you continue, please remember:
- Custom attributes are very flexible, but make sure you aren’t implementing a feature which already exists in Centra, like translations, folders, categories, relations, external IDs, etc.
- Some attributes are defined by plugins, for example Ingrid (order-level), Voyado (order, customer) or Google Merchant (product, variant).
From the Integration API’s perspective, one can always fetch all attribute types like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
query attributeTypes($filter: AttributeTypeFilter) {
attributeTypes(where: $filter) {
name
description
isMapped
isMulti
objectType
elements {
key
description
kind
isMulti
paramsJSON
}
}
}
Mind the filter, which may be useful if your integration only handles a subset of object types.
Make sure you know the difference between mapped and dynamic attributes, as a proper configuration can be crucial for efficient integrations. A limited, repeatable set of values is usually better represented by a mapped attribute, which is defined once, and then assigned by id
. Dynamic attributes should be used for high-cardinality properties assigned to each object individually.
Assigning attributes#
Integration API has a few ways of assigning attributes, suitable for different scenarios:
- The universal
assignAttributes
mutation, which can assign an arbitrary number of attributes (mapped and dynamic) to a single object of any type. - The
assignAttributesBatch
, which can be used to do the same, but for up to 100 objects at once. It’s the most efficient way to update attributes on a lot of objects. - Some
create*
andupdate*
mutations:Product
,ProductVariant
,Category
,Campaign
andVoucher
. These are useful if you want to combine attribute updates with other updates to reduce the number of mutations you need to run.
Note that in all the methods above, objects can be assigned all attributes and all dynamic element values at once. It’s the first and most important level of batching, which every integration should implement in their calls.
Example: assign attributes to two product variants at once#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
mutation multiAssignAttrs {
assignAttributesBatch(input: [
{
objectType: ProductVariant
objectId: 1334
dynamicAttributes: [
{
attributeTypeName: "var_season_code"
attributeElementKey: "text"
attributeElementValue: "SUMMER"
}
]
mappedAttributes: [
{
attributeTypeName: "google_merchant_material"
attributeId: 42
},
{
attributeTypeName: "google_merchant_pattern"
attributeId: 82
}
]
},
{
objectType: ProductVariant
objectId: 1446
mappedAttributes: [
{
attributeTypeName: "google_merchant_material"
attributeId: 43
},
{
attributeTypeName: "google_merchant_pattern"
attributeId: 83
}
]
}
]) {
userErrors { message, path }
userWarnings { message, path }
}
}
Now, that's efficient!
Setting “choice” elements (boolean
and select
) should be done using their defined value.
Fetching attribute values#
It may be surprising that getting attribute values isn’t as easy as setting them, but that’s because elements have different types, and each type has its own fields. Look at these fragments for a universal way of fetching all values.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fragment attributes on ObjectWithAttributes {
attributes {
type { name }
description
...on MappedAttribute { id, name }
elements { ...attributeElement }
}
}
fragment attributeElement on AttributeElement {
key
description
kind # specifies a UI element to display
...on AttributeStringElement { value }
...on AttributeChoiceElement { selectedValue, selectedValueName }
...on AttributeFileElement { url }
...on AttributeImageElement { url, mimeType, width, height }
}
And now you can use them to enhance your queries:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
query singleProduct($myId: String!) {
product(externalId: $myId) {
id
name
status
...attributes
variants {
id
name
...attributes
}
displays(where: {storeId: [1]}) {
id
uri
...attributes
}
}
}
Translations#
Every string element in attributes can be translated. And by the way, that’s one reason to prefer mapped over dynamic attributes – you only need to translate them once.
You can discover a list of translatable attribute elements by issuing the following query:
1
2
3
4
5
6
query translatable {
translatableFields {
objectType
fields
}
}
For each objectType
you will find its translatable fields, including custom attributes – but only the dynamic ones. They are easy to recognize, as they have a form of attribute.attribute_name.element_key
. For example:
1
2
3
4
5
6
7
8
9
{
"objectType": "Display",
"fields": [
"name",
"uri",
"description",
"attributes.prd_meta_title.text"
]
}
This means that you can (and probably should) set translations for them together, in a single call per object (a Display
in this example):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mutation translateWithAttrs {
setTranslations(
input: {
language: { id: 2 }
objectType: Display
objectId: 341
translations: [
{ field: "name", value: "Mia bela robo" }
{ field: "uri", value: "bela-robo" }
{ field: "description", value: null } # delete this one
{ field: "attributes.prd_meta_title.text", value: "Ĉiuj envios vin" }
]
}
) {
userErrors { message, path }
userWarnings { message, path }
translatedObject {
... on Display { name uri }
translations(where: { languageId: 2 }) {
fields { field, value }
}
}
}
}
Mapped attributes are entities with their own IDs; their translatable fields are returned together like this, under one MappedAttribute
object type:
1
2
3
4
5
6
7
8
9
10
11
{
"objectType": "MappedAttribute",
"fields": [
"category_text.name",
"google_merchant_product_group.text",
"google_merchant_product_type.text",
"sh_custom_badge.name",
"sh_custom_badge.text_color",
"wash_instructions_general.desc"
]
}
In order to translate a mapped attribute, you would pick its fields from this list, and update them like so:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mutation translateMappedAttribute {
setTranslations(
input: {
language: { id: 2 }
objectType: MappedAttribute
objectId: 16 # this one is a "sh_custom_badge" type
translations: [
{ field: "sh_custom_badge.name", value: "Ruĝa" }
{ field: "sh_custom_badge.text_color", value: "#D43C20" }
]
}
) {
userErrors { message, path }
userWarnings { message, path }
translatedObject {
... on MappedAttribute { id }
translations(where: { languageId: 2 }) {
fields { field, value }
}
}
}
}