Using Custom Types and Custom Fields

Sometimes the fields present on a commercetools resource do not meet your business' needs. This tutorial shows how to extend standard commercetools platform resources with custom types and custom fields.

In this tutorial, you will extend the Customer resource representation. You can find a list of all customizable commercetools resources in the API Reference documentation.

Let's say you operate an online business selling shoes. You want to provide personalization for your logged in customers by storing their preferred shoe size. By storing their shoe size as a part of their customer data, you can auto-select their shoe size when navigating to a product detail page, or provide search results with only shoes in their size.

To do this, we need to extend the standard Customer object with a Custom Type containing a CustomField.

Customization in commercetools

Adding a custom field is a two step process. First, we need to create a Custom Type by submitting a TypeDraft with, among other things, a resourceTypeId and a FieldDefinition. The FieldDefinition describes the new custom field, and the resourceTypeId indicates which resources are customizable using the Custom Type.

Creating a Custom Type

Before we can add the preferredShoeSize field to the Customer object, we need to create a new Custom Type which contains the preferredShoeSizeCustomField.

To do this, we send a POST request to the /<project-id>/types endpoint with the following payload:

final String key = "customer-preferredShoeSize";
final LocalizedString name = en("preferredShoeSize");
final Set<String> resourceTypeIds = asSet("customer");
final String fieldDefinitionName = "preferredShoeSize";
final LocalizedString localizedLabelString = LocalizedString.of(Locale.ENGLISH, "Preferred Shoe Size");
final FieldDefinition fieldDefinition = FieldDefinition.of(LocalizedStringFieldType.of(),
fieldDefinitionName,
localizedLabelString,
false,
TextInputHint.SINGLE_LINE);
final TypeDraftDsl typeDraft = TypeDraftBuilder.of(key, name, resourceTypeIds)
.fieldDefinitions(asList(fieldDefinition))
.build();
return client.execute(TypeCreateCommand.of(typeDraft));

The resourceTypeIds in this request indicate which resources we can use the custom field with. We also specify that we want to use a LocalizedString type.

After successful creation the response contains the new Custom Type with the preferredShoeSize field:

{
"id": "52b750ed-37da-4535-8a5f-75fec9df7857",
"version": 1,
"key": "customer-preferredShoeSize",
"name": {
"en": "Additional field to store preferred shoe size"
},
"resourceTypeIds": [
"customer"
],
"fieldDefinitions": [
{
"name":"preferredShoeSize",
"label":{
"en":"Preferred Shoe Size"
},
"required": false,
"type": {
"name": "LocalizedString"
},
"inputHint": "SingleLine"
}
],
"createdAt": "2015-10-02T09:44:24.628Z",
"lastModifiedAt": "2015-10-02T09:44:24.628Z"
}

Creating a Customer with the preferredShoeSize CustomField

All other fields of the Customer object are inherited from the standard resource, but we can now create a new Customer with the additional information about shoe size.

To do so, submit a POST request to the /<project-id>/customers API endpoint with following payload:

final CustomFieldsDraft customFieldDraft = CustomFieldsDraftBuilder.ofTypeKey("customer-preferredShoeSize")
.addObject("preferredShoeSize", LocalizedString.of(Locale.ENGLISH, "38"))
.build();
final CustomerDraft customerDraft = CustomerDraftDsl
.of(CustomerName.ofFirstAndLastName("John", "Doe"), "john.doe@example.com", "secret123")
.withCustomerNumber("Registered-0042")
.withCustom(customFieldDraft);
final CustomerCreateCommand sphereRequest = CustomerCreateCommand.of(customerDraft);
return client.execute(sphereRequest);

You should receive a response to that request with the code 201 containing the customized customer resource:

{
"customer": {
"id": "85c8d7a1-da11-477f-8ad7-a19c6c274f71",
"version": 1,
"lastMessageSequenceNumber": 1,
"createdAt": "2018-12-14T14:06:05.555Z",
"lastModifiedAt": "2018-12-14T14:06:05.555Z",
"customerNumber": "Registered-0042",
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"password": "aVlInP9GSgTfLob/D619djMauHcxyXvhhYKZUtTjm3s=$3S3KeoGMjHXiHpVr90QoSydU3O1u2qcXcrvsu9dWHao=",
"addresses": [],
"shippingAddressIds": [],
"billingAddressIds": [],
"isEmailVerified": false,
"custom": {
"type": {
"typeId": "type",
"id": "b9c5e724-8d86-485b-ae1e-a1d3bcc8deae"
},
"fields": {
"preferredShoeSize": {
"en": "38"
}
}
}
}
}

Update an existing Customer with a CustomField

What happens if we want to use the CustomField on an existing customer? Can we update existing Customer resources with the custom field?

Yes, we can. The API provides update actions for that.

When we created the "John Doe" customer, we used the customer-preferredShoeSize customer type instead of the default. To use the custom field on an existing customer, we need to have them use the custom-preferredShoeSize type so we can use the preferredShoeSize field.

The SetCustomType update action provides this functionality. Let's make the existing customer "Jane Roe" a customer-preferredShoeSize customer type by sending the payload below with the update request to the Customer API endpoint:

POST /{project-id}/customers/{id-of-customer-jane-roe} with following payload:

final SetCustomType setCustomTypeUpdateAction = SetCustomType.ofTypeKeyAndObjects("customer-preferredShoeSize", Collections.emptyMap());
final CustomerUpdateCommand customerUpdateCommand = CustomerUpdateCommand.of(customer, setCustomTypeUpdateAction);
return client.execute(customerUpdateCommand);

We can find the ID of the customer-preferredShoeSize number by Querying the CustomTypes API endpoint if needed.

The response to the update action shows that the customer now contains the custom type:

{
"id": "85c8d7a1-da11-477f-8ad7-a19c6c274f71",
"version": 2,
"lastMessageSequenceNumber": 1,
"createdAt": "2018-12-14T14:06:05.555Z",
"lastModifiedAt": "2018-12-14T14:25:57.885Z",
"customerNumber": "Registered-0042",
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"password": "aVlInP9GSgTfLob/D619djMauHcxyXvhhYKZUtTjm3s=$3S3KeoGMjHXiHpVr90QoSydU3O1u2qcXcrvsu9dWHao=",
"addresses": [],
"shippingAddressIds": [],
"billingAddressIds": [],
"isEmailVerified": false,
"custom": {
"type": {
"typeId": "type",
"id": "b9c5e724-8d86-485b-ae1e-a1d3bcc8deae"
},
"fields": {}
}
}

As you can see the custom type has been set, but the fields are empty:

fields:{}

Due to this we'll have to give the preferred shoe size information for "Jane Roe" in another update action called Set CustomField.

POST /{project-id}/customers/{id-of-customer-jane-roe} with following payload:

final SetCustomField setCustomFieldUpdateAction = SetCustomField
.ofObject("preferredShoeSize", LocalizedString.of(Locale.ENGLISH, "38"));
final CustomerUpdateCommand customerUpdateCommand2 = CustomerUpdateCommand
.of(customerWithCustomType, setCustomFieldUpdateAction);
return client.execute(customerUpdateCommand2);

In the response we'll now find the preferredShoeSize field with the appropriate value:

fields:{
"preferredShoeSize": {
"en":"38"
}
}

That's it. We've updated an existing Customer with a CustomField.

Query Customer with CustomField

Let's use the custom field preferredShoeSize for queries on customers.

Imagine we have extra stock for small shoe sizes in the store. We can query all the customers who have a preferredShoeSize of 38 and target them with a discount code. We'll filter by this value for the English language part of the Localized String in preferredShoeSize by the predicate: preferredShoeSize(en="38").
Since preferredShoeSize is a CustomField we'll need to mark it like that, like so: custom(fields(preferredShoeSize(en="38")))

The complete URL-encoded query request on the Customers endpoint is as follows:

GET {projectKey}/customers?where=custom(fields(preferredShoeSize(en%3D%2238%22)))
final CustomerQuery customerQuery = CustomerQuery.of()
.withPredicates(m -> m.custom().fields().ofLocalizedString("preferedShoeSize").locale(Locale.ENGLISH).is("38"));
return client.execute(customerQuery);

The response to the query should only contain customers with a preferred shoe size of 38 in your project.

The use case tackled in this tutorial is just one example for using CustomFields on the Customer resource.

Plenty of other use cases exist, for example:

  • Storing loyalty points for each purchase by a customer.
  • Adding contextual in-cart promotions by adding fields on cart LineItems.

And more!

The Custom Types and CustomFields give you the flexibility to fit the commercetools platform resources to your requirements.