Adding a field to the checkout
Projects commonly need extra fields in the checkout, such as a purchase order number, delivery instructions, or a VAT ID. This guide walks through adding a custom field end to end: schema, resolver, form component, validation, and persistence.
-
Define the custom type in Terraform
Register the custom field on the order resource in commercetools through Terraform:
resource "commercetools_type" "order_custom_fields" {key = "order-custom-fields"name = { en = "Order custom fields" }resource_type_ids = ["order"]field {name = "purchaseOrderNumber"label = { en = "Purchase order number" }type { name = "String" }}} -
Extend the GraphQL schema
Create a project-specific module that adds the field to the schema. Extend
AbstractModuleand compose it alongside the framework modules:import { AbstractModule } from "@evolve-framework/core";import { gql } from "graphql-tag";export class OrderCustomFieldsModule extends AbstractModule {typedefs = gql`extend type Order {purchaseOrderNumber: String}`;resolvers = {Order: {purchaseOrderNumber: (order) =>order.custom?.fields?.purchaseOrderNumber ?? null,},};}Add it to the checkout service’s module composition:
backend/services/checkout-commercetools/src/module.ts export const createModule = (): CompositeModule => {return new CompositeModule([new CheckoutModule({ clientFactory }),new PaymentModule({ clientFactory }),new CartModule({ clientFactory }),new OrderCustomFieldsModule(),]);}; -
Run codegen
After updating the schema, run GraphQL Code Generator to produce updated TypeScript types for the frontend:
Terminal window pnpm codegen -
Add the form component
The checkout is structured as separate step components, each with their own form and Zod validation. Create a new field component using React Hook Form:
frontend/site/src/components/checkout/purchase-order-field.tsx import { useFormContext } from "react-hook-form";export const PurchaseOrderField = () => {const { register, formState: { errors } } = useFormContext();return (<div><label htmlFor="purchaseOrderNumber">PO Number</label><inputid="purchaseOrderNumber"{...register("purchaseOrderNumber")}/>{errors.purchaseOrderNumber && (<p>{errors.purchaseOrderNumber.message}</p>)}</div>);}; -
Add Zod validation
Add the field to the relevant checkout step’s validation schema:
import { z } from "zod";export const orderDetailsSchema = z.object({// ... existing fields for this steppurchaseOrderNumber: z.string().max(50, "PO number must be 50 characters or fewer").optional(),});React Hook Form uses this schema via
zodResolverto validate the field before submission. -
Persist the value
Save the field through a commercetools
setCustomFieldaction. In the checkout service, add a resolver or API extension handler that writes the value when the order is created:const updateAction = {action: "setCustomField",name: "purchaseOrderNumber",value: input.purchaseOrderNumber,};
Further reading
Section titled “Further reading”- Data model for how Evolve’s vendor-independent schema supports extending types
- Customization for adding project-specific modules
- Frontend development for component patterns, form handling, and data fetching

