Tutorial

How To use Custom Types and Custom Fields?

The fields present on standard resources do not cover all your needs?

In this tutorial we are going to demonstrate how you can extend the standard CTP resources, like Customer to fit them to your use case. With Custom Types and CustomFields the commercetools platform provides a way to extend CTP resources according to your needs. A list of CTP resources that can be customized is maintained in the API Reference documentation.

Let’s say, we have a use case in which we’d like to address our customers with ‘Mr’, ‘Mrs’ or ‘Miss’.
Although the standard Customer object does not provide a field that is meant to store such information we can extend the resource with a CustomField for information about salutation.

Create Custom Type

Before we can add the custom field ’salutation’ to the Customer object we need to create a new Custom Type for our customised customer object that will include the CustomField.
With the resourceTypeIds we’ll tell the platform that we’d like to extend the customer resource and in the fieldDefinitions we’ll specify that we’d like to have the CustomField named ‘salutation’ of the LocalizedStringType on our ‘customer-withSalutation’.

The POST request to the API endpoint /<project-id>/types contains following payload:

#!/bin/sh
curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/types -d @- << EOF
{
  "key": "customer-withSalutation",
  "name": { "en": "additional custom field salutation" },
  "resourceTypeIds": ["customer"],
  "fieldDefinitions": [
    {
      "type":{
        "name":"LocalizedString"
      },
      "name":"salutation",
      "label":{
        "en":"Salutation",
        "de":"Anrede"
      },
      "required":false,
      "inputHint":"SingleLine"
    }
  ]
}
EOF
{
  "key": "customer-withSalutation",
  "name": { "en": "additional custom field salutation" },
  "resourceTypeIds": ["customer"],
  "fieldDefinitions": [
    {
      "type":{
        "name":"LocalizedString"
      },
      "name":"salutation",
      "label":{
        "en":"Salutation",
        "de":"Anrede"
      },
      "required":false,
      "inputHint":"SingleLine"
    }
  ]
}
 final String key = "customer-withSalutation";
 final LocalizedString name = en("additional custom field salutation");
 final Set<String> resourceTypeIds = asSet("customer");
 final String fieldDefinitionName = "salutation";
 final LocalizedString localizedLabelString = LocalizedString.of(Locale.ENGLISH, "Salutation", Locale.GERMAN, "Anrede");
 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));
<?php
 $key = 'customer-withSalutation';
 $name = LocalizedString::ofLangAndText('en', 'additional custom field salutation');
 $description= LocalizedString::of();
 $resourceTypeIds= ['customer'];
 $typeDraft = TypeDraft::ofKeyNameDescriptionAndResourceTypes($key, $name, $description, $resourceTypeIds);
 $typeDraft->setFieldDefinitions(
     FieldDefinitionCollection::of()
     ->add(
         FieldDefinition::of()
             ->setType(
                 FieldType::of()
                     ->setName('LocalizedString')
             )
         ->setName('salutation')
         ->setLabel(
             LocalizedString::of()
                 ->add('en', 'Salutation')
                 ->add('de', 'Anrede')
         )
         ->setRequired(false)
         ->setInputHint('SingleLine')
     )
 );
 $request = TypeCreateRequest::ofDraft($typeDraft);
 $response = $client->execute(TypeCreateRequest::ofDraft($typeDraft));
 $custometype = $request->mapResponse($response);

After successful creation the response will contain our new Custom Type with the ‘salutation’ field:

{
  "id": "52b750ed-37da-4535-8a5f-75fec9df7857",
  "version": 1,
  "key": "customer-withSalutation",
  "name": {
    "en": "additional custom field salutation"
  },
  "resourceTypeIds": [
    "customer"
  ],
  "fieldDefinitions": [
    {
      "name": "salutation",
      "label": {
        "en": "Salutation",
        "de": "Anrede"
      },
      "required": false,
      "type": {
        "name": "LocalizedString"
      },
      "inputHint": "SingleLine"
    }
  ],
  "createdAt": "2015-10-02T09:44:24.628Z",
  "lastModifiedAt": "2015-10-02T09:44:24.628Z"
}

Create Customer with CustomField

All other fields of the Customer object have been inherited from the standard resource so that we can now create a new Customer resource for a certain “Mr John Doe” with the additional salutation information:

Let’s do a POST request on the API endpoint /<project-id>/customers with following payload:

#!/bin/sh
curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/customers -d @- << EOF
{
  "customerNumber":"Registered-0042",
  "email":"john.doe@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "password": "secret123",
  "custom": {
    "type": {
        "key": "customer-withSalutation",
        "typeId": "type"
    },
    "fields": {
      "salutation": {
        "en":"Mr",
        "de":"Herr"
      }
    }
  }
}
EOF
{
  "customerNumber":"Registered-0042",
  "email":"john.doe@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "password": "secret123",
  "custom": {
    "type": {
      "key": "customer-withSalutation",
      "typeId": "type"
    },
    "fields": {
      "salutation": {
        "en":"Mr",
        "de":"Herr"
      }
    }
  }
}
 final CustomFieldsDraft customFieldDraft = CustomFieldsDraftBuilder.ofTypeKey("customer-withSalutation")
         .addObject("salutation", LocalizedString.of(Locale.ENGLISH, "Mr", Locale.GERMAN, "Herr"))
         .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);
<?php
 $customerNumber= 'Registered-0042';
 $email = 'email';
 $firstName ='John';
 $lastName = "Doe";
 $password = "secret123";
 $customerDraft = CustomerDraft::ofEmailNameAndPassword($email, $firstName, $lastName, $password);
 $customerDraft->setCustomerNumber($customerNumber)
     ->setCustom(
         CustomFieldObjectDraft::of()
             ->setType(
                 TypeReference::ofTypeAndKey('type', 'customer-withSalutation')
             )
             ->setFields(
                 FieldContainer::of()
                     ->set(
                         "salutation",
                         LocalizedString::of()
                             ->add('en', 'Mr')
                             ->add('de', 'Herr')
                     )
             )
 );
 $request = CustomerCreateRequest::ofDraft($customerDraft);
 $response = $client->execute(CustomerCreateRequest::ofDraft($customerDraft));
 $customer = $request->mapResponse($response);

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

{
  "customer": {
    "id": "51271b2e-11ba-4a30-aeff-a75d23739a43",
    "version": 1,
    "customerNumber": "Registered-0042",
    "email": "john.doe@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "password": "jRxZg2v6jfLpbAyLh1JTMGvrkt/czJG7vcnJmh2TUdc=$1xURWFZWwtTIMFUy0SoirDEXV3aBCsdO83jt3DzRjlw=",
    "addresses": [],
    "isEmailVerified": false,
    "custom": {
      "type": {
        "id": "52b750ed-37da-4535-8a5f-75fec9df7857",
        "typeId": "type"
      },
      "fields": {
        "salutation": {
          "en": "Mr",
          "de": "Herr"
        }
      }
    },
    "createdAt": "2015-10-02T12:55:08.311Z",
    "lastModifiedAt": "2015-10-02T12:55:08.311Z"
  }
}

Update Customer with CustomField

So far so good, that was straight forward for new customers, but what if we decided to introduce the CustomField after we created some customers already? Can we update existing Customer resources with the custom field afterwards?
Yes, we can. The API provides update actions for that.
Remember what we did before, we’ve created our customer John Doe not as a default customer, but as an instance of the customer-withSalutation type instead.
This means we’ll need to make the existing customers instances of the customer-withSalutation type in order to assign them a value in their custom salutation field.
The update action SetCustomType provides this functionality. Let’s make our existing customer “Jane Roe” a customer-withSalutation by sending the payload below with our update request to the customer API endpoint:

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

#!/bin/sh
curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}customers/{id-of-customer-jane-roe} -d @- << EOF
{
  "version":1,
  "actions":[
    {
      "action":"setCustomType",
      "type": {
        "key":"customer-withSalutation"
      }
    }
  ]
}
EOF
{
  "version":1,
  "actions":[
    {
      "action":"setCustomType",
      "type": {
        "key":"customer-withSalutation"
      }
    }
  ]
}
 final SetCustomType setCustomTypeUpdateAction = SetCustomType.ofTypeKeyAndObjects("customer-withSalutation", Collections.emptyMap());
 final CustomerUpdateCommand customerUpdateCommand = CustomerUpdateCommand.of(customer, setCustomTypeUpdateAction);
 return client.execute(customerUpdateCommand);
<?php
 $request = CustomerUpdateRequest::ofIdAndVersion($customer->getId(), $customer->getVersion());
 $request->addAction(SetCustomTypeAction::ofTypeKey('customer-withSalutation'));
 $response = $request->executeWithClient($client);
 $customer = $request->mapFromResponse($response);

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

{
  "id": "7a46be5a-4c12-4141-9f19-5c47300e5b37",
  "version": 2,
  "customerNumber": "Registered-0023",
  "email": "jane.roe@example.com",
  "firstName": "Jane",
  "lastName": "Roe",
  "password": "jRxZg2v6jfLpbAyLh1JTMGvrkt/czJG7vcnJmh2TUdc=$1xURWFZWwtTIMFUy0SoirDEXV3aBCsdO83jt3DzRjlw=",
  "addresses": [],
  "isEmailVerified": false,
  "custom": {
    "type": {
      "typeId": "type",
      "id": "52b750ed-37da-4535-8a5f-75fec9df7857"
    },
    "fields": {}
  },
  "createdAt": "2015-06-15T15:35:21.426Z",
  "lastModifiedAt": "2015-10-02T12:58:38.459Z"
}

As you can see the custom type has been set, but there is no custom field given so far; the fields are empty:

  fields:{}

So, we’ll have to give the salutation information for “Jane Roe” in another update action called Set CustomField.

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

#!/bin/sh
curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/customers/{id-of-customer-jane-roe} -d @- << EOF
{
  "version":2,
  "actions":[
    {
      "action":"setCustomField",
      "name": "salutation",
      "value":{
        "en":"Mrs",
        "de":"Frau"
      }
    }
  ]
}
EOF
{
  "version":2,
  "actions":[
    {
      "action":"setCustomField",
      "name": "salutation",
      "value":{
        "en":"Mrs",
        "de":"Frau"
      }
    }
  ]
}
 final SetCustomField setCustomFieldUpdateAction = SetCustomField
         .ofObject("salutation", LocalizedString.of(Locale.ENGLISH, "Mrs", Locale.GERMAN, "Frau"));
 final CustomerUpdateCommand customerUpdateCommand2 = CustomerUpdateCommand
         .of(customerWithCustomType, setCustomFieldUpdateAction);
 return client.execute(customerUpdateCommand2);
<?php
 $request = CustomerUpdateRequest::ofIdAndVersion($customer->getId(), $customer->getVersion());
 $request->addAction(
     SetCustomFieldAction::ofName('salutation')
         ->setValue(
             LocalizedString::of()
                 ->add('en', 'Mrs')
                 ->add('de', 'Frau')
         )
 );
 $response = $request->executeWithClient($client);
 $customer = $request->mapFromResponse($response);

In the response we’ll now find the salutation field with the appropriate value:

fields:{
  "salutation": {
    "en": "Mrs",
    "de": "Frau"
  }
}

That’s it. We are now done with updating an existing Customer with a CustomField.

Query Customer with CustomField

Let’s use the custom field salutation for queries on customers.
Imagine, for promoting our special offers on our men’s products we’d like to know all the male customers in our project. For doing so we can query for all customers that have a “Mr” in their salutation field.
We’ll filter by this value for the English language part of the Localized String in salutation by the predicate: salutation(en="Mr").
Since salutation is a CustomField we’ll need to mark it like that, like so: custom(fields(salutation(en="Mr")))

The complete URL-encoded query request on the Customers endpoint looks like below:

GET {projectKey}/customers?where=custom(fields(salutation(en%3D%22Mr%22)))
curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.sphere.io/{project-key}/customers?where=custom%28fields%28salutation%28en%3D%22Mr%22%29%29%29
 final CustomerQuery customerQuery = CustomerQuery.of()
         .withPredicates(m -> m.custom().fields().ofLocalizedString("salutation").locale(Locale.ENGLISH).is("Mr"));
 return client.execute(customerQuery);
<?php
 $request = CustomerQueryRequest::of()->where('custom(fields(salutation(en="Mr")))');
 $response = $request->executeWithClient($client);
 $customer = $request->mapFromResponse($response);

The response to that query should only contain the Misters in your project. The use case tackled in this tutorial is just one example for using CustomFields on the Customer resource, but of course there are plenty of other use cases out there we can’t even think of yet.
The Custom Types and CustomFields give you the flexibility to fit the CTP resources to your requirements.