# Add Custom Fields Sometimes the default fields present on a resource do not meet your business' needs. This tutorial shows how to extend resources with [Types](/api/projects/types.md) and [Custom Fields](/api/projects/custom-fields.md). In this tutorial, you will extend the [Customer](/urn?urn=ctp%3Aapi%3Atype%3ACustomer) resource representation. You can find a list of all customizable resources in the [API Reference documentation](/api/projects/types.md#resourcetypeid). 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](/urn?urn=ctp%3Aapi%3Atype%3ACustomer) object with a [Type](/urn?urn=ctp%3Aapi%3Atype%3AType), which defines a [CustomField](/api/projects/custom-fields.md). Learn more about using Types in our self-paced Extensibility overview module. ## Customization Adding a Custom Field is a two step process. First, we need to create a [Type](/urn?urn=ctp%3Aapi%3Atype%3AType) by submitting a [TypeDraft](/urn?urn=ctp%3Aapi%3Atype%3ATypeDraft) with, among other things, a [`resourceTypeId`](/api/projects/types.md#type) and a [FieldDefinition](/urn?urn=ctp%3Aapi%3Atype%3AFieldDefinition). The FieldDefinition describes the new Custom Field, and the `resourceTypeId` indicates which resources are customizable using the Type. ## Create a Type Before we can add the `preferredShoeSize` field to the [Customer](/urn?urn=ctp%3Aapi%3Atype%3ACustomer) object, we need to create a new [Type](/urn?urn=ctp%3Aapi%3Atype%3AType) which contains the `preferredShoeSize` [CustomField](/api/projects/custom-fields.md). To do this, we send a `POST` request to the `//types` endpoint with the following payload: ```json { "key": "customer-preferredShoeSize", "name": { "en": "Additional field to store preferred shoe size" }, "resourceTypeIds": ["customer"], "fieldDefinitions": [ { "type":{ "name":"LocalizedString" }, "name":"preferredShoeSize", "label":{ "en":"Preferred Shoe Size" }, "required":false, "inputHint":"SingleLine" } ] } ``` ```java // Create the FieldDefinition FieldDefinition fieldDefinition = FieldDefinition .builder() .type(FieldType.localizedStringBuilder().build()) .name("preferredShoeSize") .inputHint(TypeTextInputHint.SINGLE_LINE) .label( LocalizedString.builder().addValue("en", "Preferred Shoe Size").build() ) .required(false) .build(); // Create typeDraft with the FieldDefinition final TypeDraft typeDraft = TypeDraft .builder() .key("customer-preferredShoeSize") .name( LocalizedString .builder() .addValue("en", "Additional field to store preferred shoe size") .build() ) .resourceTypeIds(List.of(ResourceTypeId.CUSTOMER)) .fieldDefinitions(List.of(fieldDefinition)) .build(); // Post typeDraft and create Custom Type final Type type = apiRoot.types().post(typeDraft).executeBlocking().getBody(); ``` ```ts const customType = apiRoot .types() .post({ body: { key: "customer-preferredShoeSize", name: { "en": "Additional field to store preferred shoe size" }, resourceTypeIds: ["customer"], fieldDefinitions: [{ type: { name: "LocalizedString" }, name: "preferredShoeSize", label: { "en": "Preferred Shoe Size" }, required: false, inputHint: "SingleLine" }] } }) .execute().then(function ({ body }) { console.log(JSON.stringify(body)); }) ``` ```php add(FieldDefinitionBuilder::of()) ->withType(CustomFieldLocalizedStringTypeBuilder::of()->build()) ->withName('preferredShoeSize') ->withLabel( LocalizedStringBuilder::of() ->put('en', 'Preferred Shoe Size') ->build(), ) ->withRequired(false) ->withInputHint('SingleLine') ->build(); // Create typeDraft with the FieldDefinition $typeDraft = TypeDraftBuilder::of() ->withKey('customer-preferredShoeSize') ->withName( LocalizedStringBuilder::of() ->put('en', 'Additional field to store preferred shoe size') ->build(), ) ->withResourceTypeIds(['customer']) ->withFieldDefinitions($fieldCollection) ->build(); // Post typeDraft and create Custom Type $customType = $apiRoot ->types() ->post($typeDraft) ->execute(); ``` ```cs // Create the FieldDefinition var fieldDefinition = new FieldDefinition() { Type = new FieldType() { Name = "LocalizedString" }, Name = "preferredShoeSize", InputHint = ITypeTextInputHint.SingleLine, Label = new LocalizedString() { { "en", "Preferred Shoe Size" } }, Required = false }; // Create typeDraft with the FieldDefinition var typeDraft = new TypeDraft() { Key = "customer-preferredShoeSize", Name = new LocalizedString() { { "en", "Additional field to store preferred shoe size" } }, ResourceTypeIds = new List() { IResourceTypeId.Customer }, FieldDefinitions = new List() { fieldDefinition } }; // Post typeDraft and create Custom Type var customType = projectApiRoot .Types() .Post(typeDraft) .ExecuteAsync() .Result; ``` ```sh #!/bin/sh curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.{region}.commercetools.com/{projectKey}/types -d @- << EOF { "key": "customer-preferredShoeSize", "name": { "en": "Additional field to store preferred shoe size" }, "resourceTypeIds": ["customer"], "fieldDefinitions": [ { "type":{ "name":"LocalizedString" }, "name":"preferredShoeSize", "label":{ "en":"Preferred Shoe Size" }, "required":false, "inputHint":"SingleLine" } ] } EOF ``` 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 [Type](/urn?urn=ctp%3Aapi%3Atype%3AType) with the `preferredShoeSize` field: ```json { "id": "b9c5e724-8d86-485b-ae1e-a1d3bcc8deae", "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" } ``` ## Create 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](/urn?urn=ctp%3Aapi%3Atype%3ACustomer) with the additional information about shoe size. To do so, submit a `POST` request to the `//customers` API endpoint with following payload: ```json { "customerNumber":"Registered-0042", "email":"john.doe@example.com", "firstName": "John", "lastName": "Doe", "password": "secret123", "custom": { "type": { "key": "customer-preferredShoeSize", "typeId": "type" }, "fields": { "preferredShoeSize": { "en":"38" } } } } ``` ```java // Create the customerDraft CustomerDraft customerDraft = CustomerDraft .builder() .customerNumber("Registered-0042") .email("john.doe@example.com") .firstName("John") .lastName("Doe") .password("secret123") .custom(builder -> builder .type(typeBuilder -> typeBuilder.key("customer-preferredShoeSize")) .fields(fieldBuilder -> fieldBuilder.addValue( "preferredShoeSize", LocalizedString.of(Locale.ENGLISH, "38") ) ) ) .build(); // Post the customerDraft and create the new Customer Customer newCustomer = apiRoot .customers() .post(customerDraft) .executeBlocking() .getBody() .getCustomer(); ``` ```ts const newCustomer = apiRoot .customers() .post({ body: { customerNumber: "Registered-0042", email: "john.doe@example.com", firstName: "John", lastName: "Doe", password: "secret123", custom: { type: { key: "customer-preferredShoeSize", typeId: "type" }, fields: { "preferredShoeSize": { "en": "38" } } } } }) .execute().then(function ({ body }) { console.log(JSON.stringify(body)); }); ``` ```php withCustomerNumber('Registered-0042') ->withEmail('john.doe@example.com') ->withFirstName('John') ->withLastName('Doe') ->withPassword('secret123') ->withCustom( CustomFieldsDraftBuilder::of() ->withType( TypeResourceIdentifierBuilder::of() ->withKey('customer-preferredShoeSize') ->build(), ) ->withFields( FieldContainerBuilder::of() ->put( 'preferredShoeSize', LocalizedStringBuilder::of() ->put('en', '38') ->build(), ) ->build(), ) ->build(), ) ->build(); // Post the customerDraft and create the new Customer $newCustomer = $apiRoot ->customers() ->post($customerDraft) ->execute() ->getCustomer(); ``` ```cs // Create the customerDraft var customerDraft = new CustomerDraft() { CustomerNumber = "Registered-0042", Email = "john.doe@example.com", FirstName = "John", LastName = "Doe", Password = "secret123", Custom = new CustomFieldsDraft() { Type = new TypeResourceIdentifier() { Key = "customer-preferredShoeSize", TypeId = IReferenceTypeId.Type }, Fields = new FieldContainer { { "preferredShoeSize", new LocalizedString() { { "en", "38" } } } } } }; // Post the customerDraft and create the new Customer var newCustomer = projectApiRoot .Customers() .Post(customerDraft) .ExecuteAsync() .Result .Customer; ``` ```sh #!/bin/sh curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.{region}.commercetools.com/{projectKey}/customers -d @- << EOF { "customerNumber":"Registered-0042", "email":"john.doe@example.com", "firstName": "John", "lastName": "Doe", "password": "secret123", "custom": { "type": { "key": "customer-preferredShoeSize", "typeId": "type" }, "fields": { "preferredShoeSize": { "en":"38" } } } } EOF ``` You should receive a response to that request with the code 201 containing the customized customer resource: ```json { "customer": { "id": "85c8d7a1-da11-477f-8ad7-a19c6c274f71", "version": 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](/api/projects/customers.md#customer) 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](/api/projects/customers.md#set-custom-type) 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: ```json { "version":1, "actions": [{ "action": "setCustomType", "type": { "id": "b9c5e724-8d86-485b-ae1e-a1d3bcc8deae", "typeId": "type" } }] } ``` ```java // Create update actions to set Custom Type CustomerUpdate updateActions = CustomerUpdate .builder() .version(1l) .plusActions(actionBuilder -> actionBuilder .setCustomTypeBuilder() .type(builder -> builder.id("b9c5e724-8d86-485b-ae1e-a1d3bcc8deae")) ) .build(); // Update the Customer Customer customerWithCustomType = apiRoot .customers() .withId("{id-of-customer-jane-roe}") .post(updateActions) .executeBlocking() .getBody(); ``` ```ts const customerWithCustomType = apiRoot .customers() .withId({ ID: "{id-of-customer-jane-roe}" }) .post({ body: { version: 1, actions: [{ action: "setCustomType", type: { id: "b9c5e724-8d86-485b-ae1e-a1d3bcc8deae", typeId: "type" } }] } }) .execute().then(function ({ body }) { console.log(JSON.stringify(body)); }); ``` ```php withVersion(1) ->withActions( CustomerUpdateActionCollection::of()->add( CustomerSetCustomTypeActionBuilder::of() ->withType( TypeResourceIdentifierBuilder::of() ->withId('b9c5e724-8d86-485b-ae1e-a1d3bcc8deae') ->build(), ) ->build(), ), ) ->build(); // Update the Customer $customerWithCustomType = $apiRoot ->customers() ->withId('{id-of-customer-jane-roe}') ->post($updateActions) ->execute(); ``` ```cs // Create update actions to set Custom Type var updateActions = new CustomerUpdate() { Version = 1, Actions = new List { ICustomerUpdateAction.SetCustomType(action => { action.Type = new TypeResourceIdentifier() { Id = "b9c5e724-8d86-485b-ae1e-a1d3bcc8deae" }; }) } }; // Update the Customer var customerWithCustomTypeResult = projectApiRoot .Customers() .WithId("{id-of-customer-jane-roe}") .Post(updateActions) .ExecuteAsync() .Result; ``` ```sh #!/bin/sh curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.{region}.commercetools.com/{projectKey}ustomers/{id-of-customer-jane-roe} -d @- << EOF { "version":1, "actions":[ { "action":"setCustomType", "type": { "key":"customer-preferredShoeSize" } } ] } EOF ``` We can find the ID of the `customer-preferredShoeSize` number by [Querying](/urn?urn=ctp%3Aapi%3Aendpoint%3A%2F%7BprojectKey%7D%2Ftypes%3AGET) the CustomTypes API endpoint if needed. The response to the update action shows that the customer now contains the Type: ```json { "id": "85c8d7a1-da11-477f-8ad7-a19c6c274f71", "version": 2, "createdAt": "2018-12-14T14:06:05.555Z", "lastModifiedAt": "2018-12-14T14:25:57.885Z", "customerNumber": "Registered-0024", "email": "jane.roe@example.com", "firstName": "Jane", "lastName": "Roe", "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 Type has been set, but there are no values for the fields: ```json fields:{} ``` Due to this we'll have to give the preferred shoe size information for "Jane Roe" in another update action called [Set CustomField](/api/projects/customers.md#set-customfield). POST `/{project-id}/customers/{id-of-customer-jane-roe}` with following payload: ```json { "version":2, "actions":[ { "action":"setCustomField", "name": "preferredShoeSize", "value":{ "en":"38" } } ] } ``` ```java // Create update actions to set Custom Field CustomerUpdate updateActions = CustomerUpdate .builder() .version(2L) .plusActions(actionBuilder -> actionBuilder .setCustomFieldBuilder() .name("preferredShoeSize") .value(LocalizedString.of(Locale.ENGLISH, "38")) ) .build(); // Update the Customer Customer customerWithCustomField = apiRoot .customers() .withId("{id-of-customer-jane-roe}") .post(updateActions) .executeBlocking() .getBody(); ``` ```ts const customerWithCustomField = apiRoot .customers() .withId({ ID: "{id-of-customer-jane-roe}" }) .post({ body: { version: 2, actions: [{ action: "setCustomField", name: "preferredShoeSize", value: { "en": "38" } }] } }) .execute().then(function ({ body }) { console.log(JSON.stringify(body)); }); ``` ```php withVersion(2) ->withActions( CustomerUpdateActionCollection::of()->add( CustomerSetCustomFieldActionBuilder::of() ->withName('preferredShoeSize') ->withValue( LocalizedStringBuilder::of() ->put('en', '38') ->build(), ) ->build(), ), ) ->build(); // Update the Customer $customerWithCustomField = $apiRoot ->customers() ->withId('{id-of-customer-jane-roe}') ->post($updateActions) ->execute(); ``` ```cs // Create update actions to set Custom Field var updateActions = new CustomerUpdate() { Version = 2, Actions = new List { ICustomerUpdateAction.SetCustomField(action => { action.Name = "preferredShoeSize"; action.Value = new LocalizedString() { { "en", "38" } }; }) } }; // Update the Customer var setCustomFieldOnCustomerResult = projectApiRoot .Customers() .WithId("{id-of-customer-jane-roe}") .Post(updateActions) .ExecuteAsync() .Result; ``` ```sh #!/bin/sh curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.{region}.commercetools.com/{projectKey}/customers/{id-of-customer-jane-roe} -d @- << EOF { "version":2, "actions":[ { "action":"setCustomField", "name": "preferredShoeSize", "value":{ "en":"38" } } ] } EOF ``` In the response we'll now find the `preferredShoeSize` field with the appropriate value: ```json fields:{ "preferredShoeSize": { "en":"38" } } ``` That's it. We've updated an existing [Customer](/urn?urn=ctp%3Aapi%3Atype%3ACustomer) with a [CustomField](/api/projects/custom-fields.md#customfields). ## 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](/api/types.md#localizedstring) in `preferredShoeSize` by the predicate: `preferredShoeSize(en="38")`. Since `preferredShoeSize` is a [CustomField](/api/projects/custom-fields.md#customfields) 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: ```bash GET {projectKey}/customers?where=custom(fields(preferredShoeSize(en%3D%2238%22))) ``` ```java CustomerPagedQueryResponse customersWithShoeSize = apiRoot .customers() .get() .withWhere("custom(fields(preferredShoeSize(en=\"38\")))") .executeBlocking() .getBody(); ``` ```ts const customersWithShoeSize = apiRoot .customers() .get({ queryArgs: { where: "custom(fields(preferredShoeSize(en=\"38\")))" } }) .execute().then(function ({ body }) { console.log(JSON.stringify(body)); }); ``` ```php customers() ->get() ->withWhere("custom(fields(preferredShoeSize(en=\"38\")))") ->execute(); ``` ```cs var customersWithShoeSizeResult = projectApiRoot .Customers() .Get() .WithWhere("custom(fields(preferredShoeSize(en=\"38\")))") .ExecuteAsync() .Result; ``` ```sh curl -sH "Authorization: Bearer ACCESS_TOKEN" https://api.{region}.commercetools.com/{projectKey}/customers?where=custom%28fields%preferredShoeSize%28en%3D%2238%22%29%29%29 ``` 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](/urn?urn=ctp%3Aapi%3Atype%3ACustomFields) on the [Customer resource](/api/projects/customers.md#customer). 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! Types and Custom Fields give you the flexibility to customize resources to the specific requirement of your business. ## Related pages - [Area overview page with navigation](/tutorials.md) - [Previous page: Subscribe to Messages on AWS EventBridge](/tutorials/subscriptions-eventbridge.md) - [Next page: Add composable Custom Types](/tutorials/composable-custom-types.md)