From b4872c1e3f7ea2170c97c44214b0991871c113b5 Mon Sep 17 00:00:00 2001 From: SaifBenChahed Date: Thu, 21 May 2026 15:15:05 -0400 Subject: [PATCH 1/5] Add additional fields extensions --- docs/documentation/core-concepts.md | 1 + docs/specification/additional-fields.md | 445 +++++++++++++++++ docs/specification/checkout-mcp.md | 2 + docs/specification/checkout.md | 8 + docs/specification/order-additional-fields.md | 142 ++++++ docs/specification/overview.md | 24 +- docs/specification/playground.md | 12 +- mkdocs.yml | 10 + .../schemas/shopping/additional_fields.json | 447 ++++++++++++++++++ 9 files changed, 1078 insertions(+), 13 deletions(-) create mode 100644 docs/specification/additional-fields.md create mode 100644 docs/specification/order-additional-fields.md create mode 100644 source/schemas/shopping/additional_fields.json diff --git a/docs/documentation/core-concepts.md b/docs/documentation/core-concepts.md index 25af0ccf5..4d6db20d1 100644 --- a/docs/documentation/core-concepts.md +++ b/docs/documentation/core-concepts.md @@ -182,6 +182,7 @@ up-to-date list. | Extension | Extends | Description | | :--- | :--- | :--- | +| `dev.ucp.shopping.additional_fields` | checkout, order | Merchant-configured checkout data collection fields and read-only order values | | `dev.ucp.shopping.discount` | checkout, cart | Discount codes and promotions | | `dev.ucp.shopping.fulfillment` | checkout | Shipping and delivery options | | `dev.ucp.shopping.ap2_mandate` | checkout | Non-repudiable authorization for autonomous commerce | diff --git a/docs/specification/additional-fields.md b/docs/specification/additional-fields.md new file mode 100644 index 000000000..b1bd6b1b8 --- /dev/null +++ b/docs/specification/additional-fields.md @@ -0,0 +1,445 @@ + + +# Additional Fields Extension + +## Overview + +Additional Fields extension allows businesses to indicate that they need extra +checkout data collection fields, and specifies how field definitions and +submitted values are shared between the platform and the business. + +**Key features:** + +- Receive business-defined required and optional fields +- Submit additional field values as `key` plus `value` entries +- Apply standard input hints for `text`, `boolean`, `date`, and `choice` +- Preserve extensibility through an open `input.type` vocabulary +- Invalid values communicated via `messages[]` with detailed error codes + +**Dependencies:** + +- Checkout Capability + +## Discovery + +Businesses advertise additional field support in their profile. The capability +extends checkout: + +```json +{ + "ucp": { + "version": "{{ ucp_version }}", + "capabilities": { + "dev.ucp.shopping.additional_fields": [ + { + "version": "{{ ucp_version }}", + "extends": "dev.ucp.shopping.checkout", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/additional-fields", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json" + } + ] + } + } +} +``` + +Platforms SHOULD check that checkout is extended before submitting additional +field values. The order read projection for captured values is documented in +[Order Additional Fields](order-additional-fields.md). + +## Schema + +When this capability is active, checkout is extended with an +`additional_fields` array. + +### Checkout Extension + +{{ extension_fields('additional_fields', 'additional-fields') }} + +### Additional Field + +{{ extension_schema_fields('additional_fields.json#/$defs/additional_field', 'additional-fields') }} + +## Input Type Details + +Each field definition includes an `input` object with a required `type` string. +Platforms SHOULD respect the declared type. Platforms that do not recognize a +type SHOULD NOT render it as a different supported type, coerce it to text, or +collect a value unless a negotiated extension defines that type. + +All non-null values are submitted as strings regardless of input type. + +### Additional Field Input + +{{ extension_schema_fields('additional_fields.json#/$defs/additional_field_input', 'additional-fields') }} + +### Standard Input Types + +This version defines the following standard types: + +| Type | What it adds | Example Value | +| :-------- | :----------------------------------------------------------------- | :----------------- | +| `text` | Text semantics and text validation hints; set `multiline: true` for multi-line text | `"PO-12345"` | +| `boolean` | Boolean semantics represented as strings | `"true"` | +| `date` | Calendar date semantics and date bounds | `"2026-03-15"` | +| `choice` | Select-from-list semantics and `options[]` | `"birthday"` | + +#### Text Input + +{{ extension_schema_fields('additional_fields.json#/$defs/text_input', 'additional-fields') }} + +#### Boolean Input + +{{ extension_schema_fields('additional_fields.json#/$defs/boolean_input', 'additional-fields') }} + +#### Date Input + +{{ extension_schema_fields('additional_fields.json#/$defs/date_input', 'additional-fields') }} + +#### Choice Input + +{{ extension_schema_fields('additional_fields.json#/$defs/choice_input', 'additional-fields') }} + +#### Choice Option + +{{ extension_schema_fields('additional_fields.json#/$defs/choice_option', 'additional-fields') }} + +## Extensibility + +The additional field envelope is independent from the input type vocabulary: + +- `additional_fields[].input.type` is an open string, not a closed enum. +- Standard hints are defined only for the four types above. +- Future UCP or vendor extensions can extend + `dev.ucp.shopping.additional_fields` and introduce new type identifiers and + hints without changing the field envelope. +- Child type extensions should add conditional constraints for their + `input.type` value using `allOf`, following the normal UCP schema + composition pattern. + +For vendor-defined types, use the vendor namespace in the type identifier, for +example `com.example.file_upload`. + +## Operations + +Additional field values are submitted via standard checkout create and update +operations. + +**Request behavior:** + +- **Replacement semantics**: Submitting `additional_fields` replaces any + previously submitted additional field values +- **Retain values**: Platforms must include each field value they want to keep + in the replacement set +- **Clear values**: Omit a configured field from the replacement set, or send + `"value": null` for that field key +- **Request format**: Platforms submit only `key` and `value` for each entry. + Definition properties are ignored if present +- **Unknown values**: Entries whose `key` does not match a configured field are + ignored by the business + +**Response behavior:** + +- `additional_fields` contains all configured fields with current values merged + into each entry +- `value` is `null` for fields that have not been submitted or have been + cleared by replacement +- Invalid values communicated via `messages[]` (see below) + +## Invalid Values + +When a submitted additional field value cannot be accepted, businesses +communicate this via the `messages[]` array: + +```json +{ + "status": "incomplete", + "messages": [ + { + "type": "error", + "severity": "recoverable", + "code": "additional_field_invalid_value", + "path": "$.additional_fields[?@.key=='po_number']", + "content": "Purchase Order Number is required" + } + ] +} +``` + +> **Implementation guidance:** Required or invalid additional field values +> prevent checkout completion but can be corrected by the platform. Businesses +> SHOULD use `type: "error"` and `severity: "recoverable"` for these cases. + +| Severity | Scenario | Platform Action | +| :------------ | :------------------------------------------------------------ | :---------------------------------------- | +| `recoverable` | Value is missing, too long, in the wrong format, or otherwise invalid | Resubmit with a corrected value via Update Checkout | + +**Error codes for additional field validation:** + +| Code | Description | +| :------------------------------- | :----------------------------------------------------------------- | +| `additional_field_invalid_value` | Field value is missing, empty, or fails business validation. | + +## Examples + +### Checkout with additional field definitions + +When a checkout is created or updated, the business response includes the +additional fields it needs. Values are `null` until submitted or when cleared by +replacement. + +=== "Response" + + ```json + { + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "input": { "type": "text", "pattern": "^PO-\\d+$" }, + "required": true, + "description": "Required for B2B orders. Must match format PO-.", + "value": null + }, + { + "key": "gift_wrap", + "label": "Gift Wrap", + "input": { "type": "boolean" }, + "required": false, + "description": "Add gift wrapping for $5", + "value": null + }, + { + "key": "gift_wrap_type", + "label": "Gift Wrap Type", + "input": { + "type": "choice", + "options": [ + { "value": "classic", "label": "Classic" }, + { "value": "birthday", "label": "Birthday" }, + { "value": "holiday", "label": "Holiday" } + ] + }, + "required": false, + "description": "Select the gift wrap style", + "value": null + }, + { + "key": "delivery_date", + "label": "Preferred Delivery Date", + "input": { "type": "date", "min": "2026-03-15", "max": "2026-12-31" }, + "required": false, + "description": "Select a delivery date within the next 9 months", + "value": null + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "input": { "type": "text", "multiline": true, "max_length": 500 }, + "required": false, + "description": "Any special delivery or handling instructions", + "value": null + } + ] + } + ``` + +### Submitting additional field values + +The platform submits the full replacement set of additional field values it +wants to retain. Configured fields omitted from the request have no submitted +value after the update. + +=== "Request" + + ```json + { + "additional_fields": [ + { "key": "po_number", "value": "PO-2026-001234" }, + { "key": "gift_wrap", "value": "true" }, + { "key": "gift_wrap_type", "value": "birthday" }, + { "key": "delivery_date", "value": "2026-04-01" }, + { + "key": "special_instructions", + "value": "Please leave at the reception desk.\nRing buzzer #4." + } + ] + } + ``` + +=== "Response" + + ```json + { + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "input": { "type": "text", "pattern": "^PO-\\d+$" }, + "required": true, + "description": "Required for B2B orders. Must match format PO-.", + "value": "PO-2026-001234" + }, + { + "key": "gift_wrap", + "label": "Gift Wrap", + "input": { "type": "boolean" }, + "required": false, + "description": "Add gift wrapping for $5", + "value": "true" + }, + { + "key": "gift_wrap_type", + "label": "Gift Wrap Type", + "input": { + "type": "choice", + "options": [ + { "value": "classic", "label": "Classic" }, + { "value": "birthday", "label": "Birthday" }, + { "value": "holiday", "label": "Holiday" } + ] + }, + "required": false, + "description": "Select the gift wrap style", + "value": "birthday" + }, + { + "key": "delivery_date", + "label": "Preferred Delivery Date", + "input": { "type": "date", "min": "2026-03-15", "max": "2026-12-31" }, + "required": false, + "description": "Select a delivery date within the next 9 months", + "value": "2026-04-01" + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "input": { "type": "text", "multiline": true, "max_length": 500 }, + "required": false, + "description": "Any special delivery or handling instructions", + "value": "Please leave at the reception desk.\nRing buzzer #4." + } + ] + } + ``` + +### Clearing a value + +Because `additional_fields` uses replacement semantics, the platform includes +the values it wants to retain. It can clear a field by sending `null` for that +key, or by omitting the configured field from the replacement set. + +=== "Request" + + ```json + { + "additional_fields": [ + { "key": "po_number", "value": "PO-2026-001234" }, + { "key": "gift_wrap", "value": "true" }, + { "key": "gift_wrap_type", "value": "birthday" }, + { "key": "special_instructions", "value": null } + ] + } + ``` + +=== "Response" + + ```json + { + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "input": { "type": "text", "pattern": "^PO-\\d+$" }, + "required": true, + "description": "Required for B2B orders. Must match format PO-.", + "value": "PO-2026-001234" + }, + { + "key": "gift_wrap", + "label": "Gift Wrap", + "input": { "type": "boolean" }, + "required": false, + "description": "Add gift wrapping for $5", + "value": "true" + }, + { + "key": "gift_wrap_type", + "label": "Gift Wrap Type", + "input": { + "type": "choice", + "options": [ + { "value": "classic", "label": "Classic" }, + { "value": "birthday", "label": "Birthday" }, + { "value": "holiday", "label": "Holiday" } + ] + }, + "required": false, + "description": "Select the gift wrap style", + "value": "birthday" + }, + { + "key": "delivery_date", + "label": "Preferred Delivery Date", + "input": { "type": "date", "min": "2026-03-15", "max": "2026-12-31" }, + "required": false, + "description": "Select a delivery date within the next 9 months", + "value": null + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "input": { "type": "text", "multiline": true, "max_length": 500 }, + "required": false, + "description": "Any special delivery or handling instructions", + "value": null + } + ] + } + ``` + +### Invalid additional field value + +When an additional field cannot be accepted, the field still appears in +`additional_fields` and the rejection is communicated via `messages[]`. + +=== "Response" + + ```json + { + "status": "incomplete", + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "input": { "type": "text", "pattern": "^PO-\\d+$" }, + "required": true, + "description": "Required for B2B orders. Must match format PO-.", + "value": null + } + ], + "messages": [ + { + "type": "error", + "severity": "recoverable", + "code": "additional_field_invalid_value", + "path": "$.additional_fields[?@.key=='po_number']", + "content": "Purchase Order Number is required" + } + ] + } + ``` diff --git a/docs/specification/checkout-mcp.md b/docs/specification/checkout-mcp.md index 303e26051..72e04bf38 100644 --- a/docs/specification/checkout-mcp.md +++ b/docs/specification/checkout-mcp.md @@ -141,6 +141,7 @@ Maps to the [Create Checkout](checkout.md#create-checkout) operation. * `checkout` ([Checkout](checkout.md#create-checkout)): **Required**. Contains the initial checkout session data and optional extensions. * Extensions (Optional): + * `dev.ucp.shopping.additional_fields`: [Additional Fields](additional-fields.md) * `dev.ucp.shopping.buyer_consent`: [Buyer Consent](buyer-consent.md) * `dev.ucp.shopping.fulfillment`: [Fulfillment](fulfillment.md) * `dev.ucp.shopping.discount`: [Discount](discount.md) @@ -394,6 +395,7 @@ Maps to the [Update Checkout](checkout.md#update-checkout) operation. * `checkout` ([Checkout](checkout.md#update-checkout)): **Required**. Contains the updated checkout session data. * Extensions (Optional): + * `dev.ucp.shopping.additional_fields`: [Additional Fields](additional-fields.md) * `dev.ucp.shopping.buyer_consent`: [Buyer Consent](buyer-consent.md) * `dev.ucp.shopping.fulfillment`: [Fulfillment](fulfillment.md) * `dev.ucp.shopping.discount`: [Discount](discount.md) diff --git a/docs/specification/checkout.md b/docs/specification/checkout.md index b0b6144e4..c9a83ccff 100644 --- a/docs/specification/checkout.md +++ b/docs/specification/checkout.md @@ -51,6 +51,14 @@ Fulfillment is optional in the checkout object. This is done to enable a platform to perform checkout for digital goods without needing to furnish fulfillment details more relevant for physical goods. +### Additional Fields + +Additional checkout data collection is modelled as an extension. When the +[Additional Fields Extension](additional-fields.md) is negotiated, businesses +can return an `additional_fields` array containing field definitions and current +values. Platforms submit updates as `key` plus `value` entries through standard +checkout create and update operations. + ### Checkout Status Lifecycle The checkout `status` field indicates the current phase of the session and diff --git a/docs/specification/order-additional-fields.md b/docs/specification/order-additional-fields.md new file mode 100644 index 000000000..6efbb8939 --- /dev/null +++ b/docs/specification/order-additional-fields.md @@ -0,0 +1,142 @@ + + +# Order Additional Fields Extension + +## Overview + +Order Additional Fields is the order read projection of the +[Additional Fields Extension](additional-fields.md). Checkout uses additional +fields to collect values; order uses compact read-only summaries of the values +captured during checkout. + +**Key features:** + +- Receive captured additional field values on order read responses +- Preserve field context with `key`, `label`, `label_content_type`, + `description`, and `input.type` +- Exclude checkout-only collection details such as `required` and validation + hints + +**Dependencies:** + +- Order Capability + +## Discovery + +Businesses advertise additional field support in their profile. For order read +responses, the capability extends order: + +```json +{ + "ucp": { + "version": "{{ ucp_version }}", + "capabilities": { + "dev.ucp.shopping.additional_fields": [ + { + "version": "{{ ucp_version }}", + "extends": "dev.ucp.shopping.order", + "spec": "https://ucp.dev/{{ ucp_version }}/specification/order-additional-fields", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json" + } + ] + } + } +} +``` + +Businesses MAY advertise additional field support for checkout only, order only, +or both. Platforms SHOULD check which resources are extended before expecting +additional fields on order read responses. + +## Schema + +When this capability is active for order, order is extended with an +`additional_fields` array containing compact read-only field summaries. + +### Order Additional Field + +{{ extension_schema_fields('additional_fields.json#/$defs/order_additional_field', 'order-additional-fields') }} + +### Order Additional Field Input + +{{ extension_schema_fields('additional_fields.json#/$defs/order_additional_field_input', 'order-additional-fields') }} + +### Order Choice Input + +{{ extension_schema_fields('additional_fields.json#/$defs/order_choice_input', 'order-additional-fields') }} + +## Operations + +Order additional fields are returned through order read responses and order +event payloads. They are read-only: + +- Businesses MAY return `additional_fields` with values captured during checkout +- Platforms do not update order additional fields through order operations +- Each entry contains only `key`, `label`, `label_content_type`, `description`, + `input`, and `value` +- Checkout collection details such as `required` and validation hints are not + included +- Choice fields include `input.value_label` for the captured value label + +## Examples + +### Order with additional field values + +```json +{ + "additional_fields": [ + { + "key": "po_number", + "label": "Purchase Order Number", + "description": "Required for B2B orders. Must match format PO-.", + "input": { "type": "text" }, + "value": "PO-2026-001234" + }, + { + "key": "gift_wrap", + "label": "Gift Wrap", + "description": "Add gift wrapping for $5", + "input": { "type": "boolean" }, + "value": "true" + }, + { + "key": "gift_wrap_type", + "label": "Gift Wrap Type", + "description": "Gift wrap style selected for the order.", + "input": { + "type": "choice", + "value_label": "Birthday" + }, + "value": "birthday" + }, + { + "key": "delivery_date", + "label": "Preferred Delivery Date", + "description": "Preferred delivery date.", + "input": { "type": "date" }, + "value": "2026-04-01" + }, + { + "key": "special_instructions", + "label": "Special Instructions", + "description": "Any special delivery or handling instructions", + "input": { "type": "text" }, + "value": null + } + ] +} +``` diff --git a/docs/specification/overview.md b/docs/specification/overview.md index c8db0ebe5..fefb387db 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -70,12 +70,13 @@ All capability and service names **MUST** use the format: **Examples:** -| Name | Authority | Service | Capability | -| ----------------------------------- | ----------- | -------- | ---------------- | -| `dev.ucp.shopping.checkout` | ucp.dev | shopping | checkout | -| `dev.ucp.shopping.fulfillment` | ucp.dev | shopping | fulfillment | -| `dev.ucp.common.identity_linking` | ucp.dev | common | identity_linking | -| `com.example.payments.installments` | example.com | payments | installments | +| Name | Authority | Service | Capability | +| ------------------------------------ | ----------- | -------- | ----------------- | +| `dev.ucp.shopping.checkout` | ucp.dev | shopping | checkout | +| `dev.ucp.shopping.additional_fields` | ucp.dev | shopping | additional_fields | +| `dev.ucp.shopping.fulfillment` | ucp.dev | shopping | fulfillment | +| `dev.ucp.common.identity_linking` | ucp.dev | common | identity_linking | +| `com.example.payments.installments` | example.com | payments | installments | #### Spec URL Binding @@ -205,6 +206,7 @@ When an extension declares multiple parents: Extensions can be: +- **Official**: `dev.ucp.shopping.additional_fields` extends `dev.ucp.shopping.checkout` and `dev.ucp.shopping.order` - **Official**: `dev.ucp.shopping.fulfillment` extends `dev.ucp.shopping.checkout` - **Vendor**: `com.example.installments` extends `dev.ucp.shopping.checkout` @@ -1005,11 +1007,11 @@ root capability. **Selection Examples:** -| Response Type | Includes | Does NOT Include | -| ------------- | ------------------------------- | ---------------------------- | -| Checkout | checkout, discount, fulfillment | cart, order | -| Cart | cart, discount | checkout, fulfillment, order | -| Order | order | checkout, cart, discount | +| Response Type | Includes | Does NOT Include | +| ------------- | -------------------------------------------------- | ---------------------------- | +| Checkout | checkout, additional_fields, discount, fulfillment | cart, order | +| Cart | cart, discount | checkout, fulfillment, order | +| Order | order, additional_fields | checkout, cart, discount | ## Identity & Authentication diff --git a/docs/specification/playground.md b/docs/specification/playground.md index 08db1d153..05d1bdb4e 100644 --- a/docs/specification/playground.md +++ b/docs/specification/playground.md @@ -527,6 +527,14 @@ const UcpData = { schema: "https://ucp.dev/{{ ucp_version }}/schemas/shopping/discount.json" } ], + "dev.ucp.shopping.additional_fields": [ + { + extends: "dev.ucp.shopping.checkout", + version: "{{ ucp_version }}", + spec: "https://ucp.dev/{{ ucp_version }}/specification/additional-fields", + schema: "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json" + } + ], "dev.ucp.shopping.buyer_consent": [ { extends: "dev.ucp.shopping.checkout", @@ -553,8 +561,8 @@ const UcpData = { }, full: { label: "Full", - description: "Supports core + Fulfillment and Discount extensions.", - caps: ["dev.ucp.shopping.checkout", "dev.ucp.shopping.order", "dev.ucp.shopping.fulfillment", "dev.ucp.shopping.discount", "dev.ucp.shopping.buyer_consent", "dev.ucp.shopping.ap2_mandates"] + description: "Supports core checkout extensions.", + caps: ["dev.ucp.shopping.checkout", "dev.ucp.shopping.order", "dev.ucp.shopping.fulfillment", "dev.ucp.shopping.discount", "dev.ucp.shopping.additional_fields", "dev.ucp.shopping.buyer_consent", "dev.ucp.shopping.ap2_mandates"] } }, diff --git a/mkdocs.yml b/mkdocs.yml index 36cfef1f7..a67181b46 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,6 +48,7 @@ nav: - MCP: specification/checkout-mcp.md - A2A: specification/checkout-a2a.md - Embedded: specification/embedded-checkout.md + - Additional Fields Extension: specification/additional-fields.md - AP2 Mandates Extension: specification/ap2-mandates.md - Buyer Consent Extension: specification/buyer-consent.md - Discounts Extension: specification/discount.md @@ -72,6 +73,7 @@ nav: - Transports: - REST: specification/order-rest.md - MCP: specification/order-mcp.md + - Additional Fields Extension: specification/order-additional-fields.md - Identity Linking Capability: specification/identity-linking.md - Payment Handlers: - Guide: specification/payment-handler-guide.md @@ -300,6 +302,11 @@ plugins: per-session delegation negotiation, URL parameters, native UI event mappings, W3C Payment Request conceptual alignment, and core lifecycle messages. + - specification/additional-fields.md: >- + Additional Fields Extension, defining merchant-configured checkout + data collection fields, value submission semantics, open input + type extensibility, and standard text, boolean, date, and choice + input hints. - specification/ap2-mandates.md: >- AP2 Mandates Extension, detailing secure, cryptographically bound checkout commitments (using SD-JWT+kb digital credentials and @@ -378,6 +385,9 @@ plugins: Model Context Protocol (MCP) transport binding for the Order Capability, detailing the JSON-RPC tool interface and parameters for fetching order states. + - specification/order-additional-fields.md: >- + Order Additional Fields Extension, defining compact read-only + summaries of additional field values captured during checkout. - specification/identity-linking.md: >- User authentication via the Identity Linking Capability, specifying direct B2C/B2B OAuth 2.0 flows (with PKCE and diff --git a/source/schemas/shopping/additional_fields.json b/source/schemas/shopping/additional_fields.json new file mode 100644 index 000000000..ce7155f31 --- /dev/null +++ b/source/schemas/shopping/additional_fields.json @@ -0,0 +1,447 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/additional_fields.json", + "name": "dev.ucp.shopping.additional_fields", + "title": "Additional Fields Extension", + "description": "Extends Checkout with merchant-defined additional data collection fields and Order with read-only captured additional field values. The field envelope is stable while input.type remains open for future type extensions.", + "$defs": { + "choice_option": { + "title": "Choice Option", + "type": "object", + "description": "A selectable option for a choice additional field.", + "required": [ + "value", + "label" + ], + "properties": { + "value": { + "type": "string", + "description": "Submitted value for this option." + }, + "label": { + "type": "string", + "description": "Human-readable option label." + } + } + }, + "additional_field_input": { + "title": "Additional Field Input", + "type": "object", + "description": "Input configuration for an additional field. The type value is an open field type identifier. Platforms MUST respect the declared type and MUST NOT render or coerce unrecognized types as another supported input.", + "required": [ + "type" + ], + "additionalProperties": true, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "text" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/text_input" + } + }, + { + "if": { + "properties": { + "type": { + "const": "boolean" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/boolean_input" + } + }, + { + "if": { + "properties": { + "type": { + "const": "date" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/date_input" + } + }, + { + "if": { + "properties": { + "type": { + "const": "choice" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/choice_input" + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Field type identifier. Standard values defined by this extension include text, boolean, date, and choice. Vendors MAY define custom type identifiers using their own namespace." + } + } + }, + "text_input": { + "title": "Text Input", + "type": "object", + "description": "Text field semantics with optional validation hints. Set multiline to true for multi-line text.", + "required": [ + "type" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "text", + "description": "Use text for text fields. Set multiline to true for multi-line text.", + "examples": [ + "text" + ] + }, + "min_length": { + "type": "integer", + "minimum": 0, + "description": "Minimum number of characters accepted by the business." + }, + "max_length": { + "type": "integer", + "minimum": 0, + "description": "Maximum number of characters accepted by the business." + }, + "pattern": { + "type": "string", + "format": "regex", + "description": "Regular expression the submitted value should match." + }, + "multiline": { + "type": "boolean", + "default": false, + "description": "True when the text value is expected to contain multiple lines." + } + } + }, + "boolean_input": { + "title": "Boolean Input", + "type": "object", + "description": "Boolean field semantics. Values are still submitted as strings.", + "required": [ + "type" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "boolean", + "description": "Use boolean for true/false fields. Submit checked as true and unchecked as false.", + "examples": [ + "boolean" + ] + } + } + }, + "date_input": { + "title": "Date Input", + "type": "object", + "description": "Date field semantics with optional date range hints.", + "required": [ + "type" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "date", + "description": "Use date for calendar-date fields.", + "examples": [ + "date" + ] + }, + "min": { + "type": "string", + "format": "date", + "description": "Earliest accepted date in YYYY-MM-DD format." + }, + "max": { + "type": "string", + "format": "date", + "description": "Latest accepted date in YYYY-MM-DD format." + } + } + }, + "choice_input": { + "title": "Choice Input", + "type": "object", + "description": "Select-from-list field semantics.", + "required": [ + "type", + "options" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "choice", + "description": "Use choice when the submitted value must be selected from options.", + "examples": [ + "choice" + ] + }, + "options": { + "type": "array", + "description": "Available choices. Platforms display label and submit value.", + "items": { + "$ref": "#/$defs/choice_option" + }, + "minItems": 1 + } + } + }, + "additional_field": { + "title": "Additional Field", + "type": "object", + "description": "Definition and current value for a merchant-defined checkout field.", + "additionalProperties": true, + "required": [ + "key", + "label", + "required", + "description", + "input", + "value" + ], + "properties": { + "key": { + "type": "string", + "description": "Unique identifier for the field. Must be unique within the additional_fields array.", + "ucp_request": { + "create": "required", + "update": "required", + "complete": "omit" + } + }, + "label": { + "type": "string", + "description": "Human-readable display name for the field.", + "ucp_request": "omit" + }, + "label_content_type": { + "type": "string", + "enum": [ + "plain", + "markdown" + ], + "default": "plain", + "description": "Format for the label. Defaults to plain.", + "ucp_request": "omit" + }, + "required": { + "type": "boolean", + "description": "Whether the field must be filled before checkout can complete.", + "ucp_request": "omit" + }, + "description": { + "type": "string", + "description": "Help text or instructions for the field.", + "ucp_request": "omit" + }, + "input": { + "$ref": "#/$defs/additional_field_input", + "description": "Input control configuration.", + "ucp_request": "omit" + }, + "value": { + "type": [ + "string", + "null" + ], + "description": "Current value for the field. Null when no value has been submitted or the value has been cleared by replacement.", + "ucp_request": { + "create": "required", + "update": "required", + "complete": "omit" + } + } + } + }, + "order_additional_field_input": { + "title": "Order Additional Field Input", + "type": "object", + "description": "Input metadata for a read-only order additional field. Order input metadata includes the field type and, for choice fields, the display label for the captured value.", + "required": [ + "type" + ], + "additionalProperties": true, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "choice" + } + }, + "required": [ + "type" + ] + }, + "then": { + "$ref": "#/$defs/order_choice_input" + } + } + ], + "properties": { + "type": { + "type": "string", + "description": "Field type identifier. Standard values defined by this extension include text, boolean, date, and choice. Future extensions can define additional type identifiers.", + "examples": [ + "text", + "boolean", + "date", + "choice" + ] + } + } + }, + "order_choice_input": { + "title": "Order Choice Input", + "type": "object", + "description": "Choice input metadata included on order additional fields.", + "required": [ + "type", + "value_label" + ], + "additionalProperties": true, + "properties": { + "type": { + "type": "string", + "const": "choice", + "description": "Use choice when the captured value was selected from options.", + "examples": [ + "choice" + ] + }, + "value_label": { + "type": [ + "string", + "null" + ], + "description": "Display label for the captured value. Null when value is null." + } + } + }, + "order_additional_field": { + "title": "Order Additional Field", + "type": "object", + "description": "Read-only additional field value captured for an order.", + "additionalProperties": false, + "required": [ + "key", + "label", + "description", + "input", + "value" + ], + "properties": { + "key": { + "type": "string", + "description": "Unique identifier for the captured field." + }, + "label": { + "type": "string", + "description": "Human-readable display name for the captured field." + }, + "label_content_type": { + "type": "string", + "enum": [ + "plain", + "markdown" + ], + "default": "plain", + "description": "Format for the label. Defaults to plain." + }, + "description": { + "type": "string", + "description": "Help text or instructions associated with the captured field." + }, + "input": { + "$ref": "#/$defs/order_additional_field_input", + "description": "Input metadata for the captured field." + }, + "value": { + "type": [ + "string", + "null" + ], + "description": "Captured value for the field. Null when no value was submitted or the value was cleared before order creation." + } + } + }, + "dev.ucp.shopping.checkout": { + "title": "Checkout with Additional Fields", + "description": "Checkout extended with merchant-defined additional fields.", + "allOf": [ + { + "$ref": "checkout.json" + }, + { + "type": "object", + "properties": { + "additional_fields": { + "type": "array", + "description": "Merchant-defined additional checkout fields with current values. Requests replace the previously submitted additional field value set; configured fields omitted from the replacement set have no submitted value.", + "items": { + "$ref": "#/$defs/additional_field" + }, + "ucp_request": { + "create": "optional", + "update": "optional", + "complete": "omit" + } + } + } + } + ] + }, + "dev.ucp.shopping.order": { + "title": "Order with Additional Fields", + "description": "Order extended with read-only additional field values captured during checkout.", + "allOf": [ + { + "$ref": "order.json" + }, + { + "type": "object", + "properties": { + "additional_fields": { + "type": "array", + "description": "Read-only additional field values captured during checkout.", + "items": { + "$ref": "#/$defs/order_additional_field" + }, + "ucp_request": "omit" + } + } + } + ] + } + } +} From d89904f8ea1c06c1b1cc64bb52356fa3577093ad Mon Sep 17 00:00:00 2001 From: SaifBenChahed Date: Tue, 26 May 2026 16:10:54 -0400 Subject: [PATCH 2/5] Add additional fields config proposal --- docs/specification/additional-fields.md | 26 +++++++ docs/specification/order-additional-fields.md | 4 ++ .../schemas/shopping/additional_fields.json | 68 +++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/docs/specification/additional-fields.md b/docs/specification/additional-fields.md index b1bd6b1b8..7ff6bdf66 100644 --- a/docs/specification/additional-fields.md +++ b/docs/specification/additional-fields.md @@ -27,6 +27,7 @@ submitted values are shared between the platform and the business. - Receive business-defined required and optional fields - Submit additional field values as `key` plus `value` entries - Apply standard input hints for `text`, `boolean`, `date`, and `choice` +- Optionally narrow supported input types with capability config - Preserve extensibility through an open `input.type` vocabulary - Invalid values communicated via `messages[]` with detailed error codes @@ -118,6 +119,31 @@ This version defines the following standard types: {{ extension_schema_fields('additional_fields.json#/$defs/choice_option', 'additional-fields') }} +## Optional Type Support Config + +By default, support for `dev.ucp.shopping.additional_fields` means support for +all standard input types defined by this extension: `text`, `boolean`, `date`, +and `choice`. Platforms and businesses MAY use `config.supported_types` to +narrow that set. + +{{ extension_schema_fields('additional_fields.json#/$defs/additional_fields_config', 'additional-fields') }} + +When both parties declare `supported_types`, the active type set is the +intersection of the platform and business declarations. When either party omits +`supported_types`, that party is treated as supporting all standard types. + +```json +{ + "config": { + "supported_types": ["text", "boolean"] + } +} +``` + +Businesses MUST only return fields whose `input.type` is in the active type +set. If a required field cannot be represented using an active type, the +business SHOULD use a supported fallback or require escalation. + ## Extensibility The additional field envelope is independent from the input type vocabulary: diff --git a/docs/specification/order-additional-fields.md b/docs/specification/order-additional-fields.md index 6efbb8939..bca5d601b 100644 --- a/docs/specification/order-additional-fields.md +++ b/docs/specification/order-additional-fields.md @@ -62,6 +62,10 @@ Businesses MAY advertise additional field support for checkout only, order only, or both. Platforms SHOULD check which resources are extended before expecting additional fields on order read responses. +If `config.supported_types` narrows the active additional field input types, +order additional fields follow the same active type set as checkout additional +fields. + ## Schema When this capability is active for order, order is extended with an diff --git a/source/schemas/shopping/additional_fields.json b/source/schemas/shopping/additional_fields.json index ce7155f31..4cded5604 100644 --- a/source/schemas/shopping/additional_fields.json +++ b/source/schemas/shopping/additional_fields.json @@ -5,6 +5,38 @@ "title": "Additional Fields Extension", "description": "Extends Checkout with merchant-defined additional data collection fields and Order with read-only captured additional field values. The field envelope is stable while input.type remains open for future type extensions.", "$defs": { + "additional_fields_config": { + "title": "Additional Fields Config", + "type": "object", + "description": "Capability configuration for additional field input type support.", + "additionalProperties": false, + "properties": { + "supported_types": { + "type": "array", + "description": "Input type identifiers supported by this party. When omitted, all standard types defined by this extension are supported: text, boolean, date, and choice. The active type set is the intersection of platform and business supported types.", + "items": { + "type": "string", + "minLength": 1 + }, + "default": [ + "text", + "boolean", + "date", + "choice" + ], + "uniqueItems": true, + "minItems": 1, + "examples": [ + [ + "text", + "boolean", + "date", + "choice" + ] + ] + } + } + }, "choice_option": { "title": "Choice Option", "type": "object", @@ -442,6 +474,42 @@ } } ] + }, + "dev.ucp.shopping.additional_fields": { + "platform_schema": { + "title": "Additional Fields Capability (Platform)", + "description": "Platform-level additional fields capability configuration.", + "allOf": [ + { + "$ref": "../capability.json#/$defs/platform_schema" + }, + { + "properties": { + "config": { + "$ref": "#/$defs/additional_fields_config", + "description": "Platform-supported additional field input types." + } + } + } + ] + }, + "business_schema": { + "title": "Additional Fields Capability (Business)", + "description": "Business-level additional fields capability configuration.", + "allOf": [ + { + "$ref": "../capability.json#/$defs/business_schema" + }, + { + "properties": { + "config": { + "$ref": "#/$defs/additional_fields_config", + "description": "Additional field input types the business may return." + } + } + } + ] + } } } } From bffaae7098f1e92757711aa99e20780715e6b865 Mon Sep 17 00:00:00 2001 From: SaifBenChahed Date: Thu, 28 May 2026 10:28:53 -0400 Subject: [PATCH 3/5] Require additional fields supported type config --- docs/specification/additional-fields.md | 21 ++++++++++--------- docs/specification/order-additional-fields.md | 8 ++++--- .../schemas/shopping/additional_fields.json | 17 ++++++++------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/docs/specification/additional-fields.md b/docs/specification/additional-fields.md index 7ff6bdf66..27c4d3081 100644 --- a/docs/specification/additional-fields.md +++ b/docs/specification/additional-fields.md @@ -27,7 +27,7 @@ submitted values are shared between the platform and the business. - Receive business-defined required and optional fields - Submit additional field values as `key` plus `value` entries - Apply standard input hints for `text`, `boolean`, `date`, and `choice` -- Optionally narrow supported input types with capability config +- Declare supported input types with capability config - Preserve extensibility through an open `input.type` vocabulary - Invalid values communicated via `messages[]` with detailed error codes @@ -50,7 +50,10 @@ extends checkout: "version": "{{ ucp_version }}", "extends": "dev.ucp.shopping.checkout", "spec": "https://ucp.dev/{{ ucp_version }}/specification/additional-fields", - "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json" + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json", + "config": { + "supported_types": ["text", "boolean", "date", "choice"] + } } ] } @@ -119,18 +122,16 @@ This version defines the following standard types: {{ extension_schema_fields('additional_fields.json#/$defs/choice_option', 'additional-fields') }} -## Optional Type Support Config +## Type Support Config -By default, support for `dev.ucp.shopping.additional_fields` means support for -all standard input types defined by this extension: `text`, `boolean`, `date`, -and `choice`. Platforms and businesses MAY use `config.supported_types` to -narrow that set. +Platform and business profile declarations for +`dev.ucp.shopping.additional_fields` MUST include `config.supported_types`. +This keeps type support explicit as the extension evolves. {{ extension_schema_fields('additional_fields.json#/$defs/additional_fields_config', 'additional-fields') }} -When both parties declare `supported_types`, the active type set is the -intersection of the platform and business declarations. When either party omits -`supported_types`, that party is treated as supporting all standard types. +The active type set is the intersection of the platform and business +declarations. ```json { diff --git a/docs/specification/order-additional-fields.md b/docs/specification/order-additional-fields.md index bca5d601b..8a564f549 100644 --- a/docs/specification/order-additional-fields.md +++ b/docs/specification/order-additional-fields.md @@ -50,7 +50,10 @@ responses, the capability extends order: "version": "{{ ucp_version }}", "extends": "dev.ucp.shopping.order", "spec": "https://ucp.dev/{{ ucp_version }}/specification/order-additional-fields", - "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json" + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/additional_fields.json", + "config": { + "supported_types": ["text", "boolean", "date", "choice"] + } } ] } @@ -62,8 +65,7 @@ Businesses MAY advertise additional field support for checkout only, order only, or both. Platforms SHOULD check which resources are extended before expecting additional fields on order read responses. -If `config.supported_types` narrows the active additional field input types, -order additional fields follow the same active type set as checkout additional +Order additional fields follow the same active type set as checkout additional fields. ## Schema diff --git a/source/schemas/shopping/additional_fields.json b/source/schemas/shopping/additional_fields.json index 4cded5604..6980470e4 100644 --- a/source/schemas/shopping/additional_fields.json +++ b/source/schemas/shopping/additional_fields.json @@ -10,20 +10,17 @@ "type": "object", "description": "Capability configuration for additional field input type support.", "additionalProperties": false, + "required": [ + "supported_types" + ], "properties": { "supported_types": { "type": "array", - "description": "Input type identifiers supported by this party. When omitted, all standard types defined by this extension are supported: text, boolean, date, and choice. The active type set is the intersection of platform and business supported types.", + "description": "Input type identifiers supported by this party. The active type set is the intersection of platform and business supported types.", "items": { "type": "string", "minLength": 1 }, - "default": [ - "text", - "boolean", - "date", - "choice" - ], "uniqueItems": true, "minItems": 1, "examples": [ @@ -484,6 +481,9 @@ "$ref": "../capability.json#/$defs/platform_schema" }, { + "required": [ + "config" + ], "properties": { "config": { "$ref": "#/$defs/additional_fields_config", @@ -501,6 +501,9 @@ "$ref": "../capability.json#/$defs/business_schema" }, { + "required": [ + "config" + ], "properties": { "config": { "$ref": "#/$defs/additional_fields_config", From b7b1fb639b306a8e42ef19f0a84767e9ec7fc55c Mon Sep 17 00:00:00 2001 From: SaifBenChahed Date: Tue, 9 Jun 2026 10:56:52 -0400 Subject: [PATCH 4/5] addressing comments --- docs/specification/additional-fields.md | 14 ++++++++++---- docs/specification/order-additional-fields.md | 2 ++ source/schemas/shopping/additional_fields.json | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/specification/additional-fields.md b/docs/specification/additional-fields.md index 27c4d3081..d8a9db108 100644 --- a/docs/specification/additional-fields.md +++ b/docs/specification/additional-fields.md @@ -85,7 +85,8 @@ Platforms SHOULD respect the declared type. Platforms that do not recognize a type SHOULD NOT render it as a different supported type, coerce it to text, or collect a value unless a negotiated extension defines that type. -All non-null values are submitted as strings regardless of input type. +All non-null values are submitted as strings regardless of input type. `null` +represents no submitted value or a cleared value. ### Additional Field Input @@ -98,7 +99,7 @@ This version defines the following standard types: | Type | What it adds | Example Value | | :-------- | :----------------------------------------------------------------- | :----------------- | | `text` | Text semantics and text validation hints; set `multiline: true` for multi-line text | `"PO-12345"` | -| `boolean` | Boolean semantics represented as strings | `"true"` | +| `boolean` | Boolean semantics represented as `"true"` / `"false"` strings | `"true"` | | `date` | Calendar date semantics and date bounds | `"2026-03-15"` | | `choice` | Select-from-list semantics and `options[]` | `"birthday"` | @@ -142,8 +143,11 @@ declarations. ``` Businesses MUST only return fields whose `input.type` is in the active type -set. If a required field cannot be represented using an active type, the -business SHOULD use a supported fallback or require escalation. +set. If the active type set is empty, or if a required field cannot be +represented using an active type, the business MUST NOT return unsupported +additional fields. When checkout cannot complete without that data, the +business MUST return `status: "requires_escalation"` and provide +`continue_url`, following the Checkout escalation flow. ## Extensibility @@ -183,6 +187,8 @@ operations. - `additional_fields` contains all configured fields with current values merged into each entry +- Platforms SHOULD render additional fields in the order returned by the + business - `value` is `null` for fields that have not been submitted or have been cleared by replacement - Invalid values communicated via `messages[]` (see below) diff --git a/docs/specification/order-additional-fields.md b/docs/specification/order-additional-fields.md index 8a564f549..96758aaae 100644 --- a/docs/specification/order-additional-fields.md +++ b/docs/specification/order-additional-fields.md @@ -97,6 +97,8 @@ event payloads. They are read-only: - Checkout collection details such as `required` and validation hints are not included - Choice fields include `input.value_label` for the captured value label +- Platforms SHOULD render order additional fields in the order returned by the + business ## Examples diff --git a/source/schemas/shopping/additional_fields.json b/source/schemas/shopping/additional_fields.json index 6980470e4..cedd40204 100644 --- a/source/schemas/shopping/additional_fields.json +++ b/source/schemas/shopping/additional_fields.json @@ -172,7 +172,7 @@ "boolean_input": { "title": "Boolean Input", "type": "object", - "description": "Boolean field semantics. Values are still submitted as strings.", + "description": "Boolean field semantics. Non-null values MUST be the exact strings \"true\" or \"false\".", "required": [ "type" ], @@ -181,7 +181,7 @@ "type": { "type": "string", "const": "boolean", - "description": "Use boolean for true/false fields. Submit checked as true and unchecked as false.", + "description": "Use boolean for true/false fields.", "examples": [ "boolean" ] From d1318ab4294c83ff58cae079e16a6461ebabaaae Mon Sep 17 00:00:00 2001 From: SaifBenChahed Date: Wed, 24 Jun 2026 09:24:47 -0400 Subject: [PATCH 5/5] docs: use positional additional field error paths --- docs/specification/additional-fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/specification/additional-fields.md b/docs/specification/additional-fields.md index d8a9db108..460bdd66f 100644 --- a/docs/specification/additional-fields.md +++ b/docs/specification/additional-fields.md @@ -206,7 +206,7 @@ communicate this via the `messages[]` array: "type": "error", "severity": "recoverable", "code": "additional_field_invalid_value", - "path": "$.additional_fields[?@.key=='po_number']", + "path": "$.additional_fields[0].value", "content": "Purchase Order Number is required" } ] @@ -470,7 +470,7 @@ When an additional field cannot be accepted, the field still appears in "type": "error", "severity": "recoverable", "code": "additional_field_invalid_value", - "path": "$.additional_fields[?@.key=='po_number']", + "path": "$.additional_fields[0].value", "content": "Purchase Order Number is required" } ]