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
- 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