Skip to main content

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.

1. 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" }
}
}

2. Extend the GraphQL schema

Create a project-specific module that adds the field to the schema. Extend AbstractGraphQLModule and compose it alongside the framework modules:

import { AbstractGraphQLModule } from "@evolve-framework/core";
import { gql } from "graphql-tag";

export class OrderCustomFieldsModule extends AbstractGraphQLModule {
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:

const module = new GraphQLCompositeModule([
new CheckoutGraphQLModule(),
new CartGraphQLModule({ config: { /* ... */ } }),
new OrderCustomFieldsModule(),
]);

3. Run codegen

After updating the schema, run GraphQL Code Generator to produce updated TypeScript types for the frontend:

pnpm codegen

4. 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>
<input
id="purchaseOrderNumber"
{...register("purchaseOrderNumber")}
/>
{errors.purchaseOrderNumber && (
<p>{errors.purchaseOrderNumber.message}</p>
)}
</div>
);
};

5. 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 step
purchaseOrderNumber: z
.string()
.max(50, "PO number must be 50 characters or fewer")
.optional(),
});

React Hook Form uses this schema via zodResolver to validate the field before submission.

6. Persist the value

Save the field through a commercetools setCustomField action. 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