diff --git a/docs/specification/additional-fields.md b/docs/specification/additional-fields.md new file mode 100644 index 00000000..dd94db5a --- /dev/null +++ b/docs/specification/additional-fields.md @@ -0,0 +1,179 @@ + + +# Additional Fields Extension + +## Overview + +The additional fields extension enables businesses to request checkout inputs +that are not covered by the core schema, and to bind those inputs to cart, +buyer, or fulfillment data via JSONPath. + +**Key features** + +- Declare required and optional additional fields with typed UI controls +- Map each field to cart, buyer, or fulfillment data using JSONPath +- Receive input in a consistent structure keyed by field `key` +- Surface validation via `messages[]` with JSONPath to the field value + +**Dependencies** + +- Checkout capability + +## Discovery + +Businesses advertise additional fields support in their profile: + +```json +{ + "ucp": { + "version": "2026-01-11", + "capabilities": { + "dev.ucp.shopping.additional_fields": [ + { + "version": "2026-01-11", + "extends": "dev.ucp.shopping.checkout", + "spec": "https://ucp.dev/specification/additional-fields", + "schema": "https://ucp.dev/schemas/shopping/additional_fields.json" + } + ] + } + } +} +``` + +## Schema + +When this extension is active, checkout gains an `additional_fields` object with +field definitions and buyer-provided values. + +### Additional Fields + +{{ extension_schema_fields('additional_fields.json#/$defs/additional_fields', 'additional-fields') }} + +### Field Definition + +{{ extension_schema_fields('additional_fields.json#/$defs/additional_field', 'additional-fields') }} + +### Validation Hints + +{{ extension_schema_fields('additional_fields.json#/$defs/validation_hints', 'additional-fields') }} + +## Input types and values + +- `single_line_text` and `multi_line_text`: string values. Platforms **SHOULD** + honor `min_length`, `max_length`, and `pattern` hints when rendering input. +- `checkbox`: boolean values. +- `date`: RFC 3339 full-date string (e.g., `2026-02-14`). +- `date_time`: RFC 3339 date-time string with offset (e.g., + `2026-02-14T15:04:05Z`). +- `default_value` must match the field's input type. + +## Validation + +- Businesses validate submitted values and return issues in `messages[]` using + `type: "error"` with `severity: "recoverable"` and `path` set to the field + value (e.g., `$.additional_fields.values.delivery_date`). +- No new message type is introduced; `type: "error"` + `severity` communicates + that the issue is blocking until resolved. +- Suggested error codes: `additional_field_required`, `additional_field_invalid` + (type or format mismatch), `additional_field_not_supported` (key unknown). +- The `validation_hints` object is a **hint** for platforms to improve UX; + businesses remain the source of truth and enforce rules via `messages[]`. + +## Operations + +- **Create/Update Checkout:** Platforms send buyer input in + `additional_fields.values`. Submitting the object replaces previously sent + values. Send an empty object to clear all additional fields. +- **Responses:** Businesses return `additional_fields.fields` to describe what + is supported, and may echo `additional_fields.values` received from the + platform. +- **Mapping:** `path` is a JSONPath from the checkout root to the data the field + influences (e.g., buyer attributes, line items, fulfillment options). It + allows platforms to render the field near its context while keeping the core + schema unchanged. + +## Example + +```json +{ + "additional_fields": { + "fields": [ + { + "key": "po_number", + "label": "PO number", + "description": "Optional purchase order for your records", + "type": "single_line_text", + "path": "$.buyer", + "validation_hints": { + "max_length": 24 + } + }, + { + "key": "gift_wrap", + "label": "Gift wrap", + "type": "checkbox", + "default_value": false, + "path": "$.line_items[?(@.id=='shirt')]" + }, + { + "key": "delivery_date", + "label": "Delivery date", + "type": "date", + "required": true, + "path": "$.fulfillment.methods[0].groups[0]", + "validation_hints": { + "min_date": "2026-02-01" + } + }, + { + "key": "gift_message", + "label": "Gift message", + "type": "multi_line_text", + "path": "$.line_items[?(@.id=='gift_box')]" + }, + { + "key": "agree_to_terms", + "label": "Agree to terms", + "type": "checkbox", + "required": true, + "path": "$" + } + ], + "values": { + "po_number": "PO-45822", + "gift_wrap": true, + "delivery_date": "2026-02-05", + "gift_message": "Happy birthday, Alex!", + "agree_to_terms": true + } + }, + "messages": [ + { + "type": "error", + "code": "additional_field_required", + "severity": "recoverable", + "path": "$.additional_fields.values.delivery_date", + "content": "Select an eligible delivery date." + } + ] +} +``` + +Use cases include buyer birthdays, order notes, line-item personalization +(e.g., monogramming), delivery dates, gift wrap and gift messages, PO numbers, +tax IDs, and terms acceptance. diff --git a/mkdocs.yml b/mkdocs.yml index 6686c99c..dd4883e7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -38,6 +38,7 @@ nav: - EP Binding: specification/embedded-checkout.md - AP2 Mandates Extension: specification/ap2-mandates.md - Buyer Consent Extension: specification/buyer-consent.md + - Additional Fields Extension: specification/additional-fields.md - Discounts Extension: specification/discount.md - Fulfillment Extension: specification/fulfillment.md - Cart Capability: diff --git a/source/schemas/shopping/additional_fields.json b/source/schemas/shopping/additional_fields.json new file mode 100644 index 00000000..ee840e88 --- /dev/null +++ b/source/schemas/shopping/additional_fields.json @@ -0,0 +1,172 @@ +{ + "$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 fields mapped to cart, buyer, or fulfillment data via JSONPath.", + "$defs": { + "input_type": { + "type": "string", + "description": "UI control type for the additional field.", + "enum": [ + "single_line_text", + "multi_line_text", + "checkbox", + "date", + "date_time" + ] + }, + "validation_hints": { + "type": "object", + "description": "Optional validation hints for platforms. Enforcement is communicated via messages.", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression hint for text inputs." + }, + "min_length": { + "type": "integer", + "minimum": 0, + "description": "Minimum characters for text inputs." + }, + "max_length": { + "type": "integer", + "minimum": 1, + "description": "Maximum characters for text inputs." + }, + "min_date": { + "type": "string", + "format": "date", + "description": "Earliest allowed date for date inputs (RFC 3339 full-date)." + }, + "max_date": { + "type": "string", + "format": "date", + "description": "Latest allowed date for date inputs (RFC 3339 full-date)." + }, + "min_date_time": { + "type": "string", + "format": "date-time", + "description": "Earliest allowed timestamp for date_time inputs (RFC 3339 date-time)." + }, + "max_date_time": { + "type": "string", + "format": "date-time", + "description": "Latest allowed timestamp for date_time inputs (RFC 3339 date-time)." + } + } + }, + "additional_field": { + "type": "object", + "description": "Definition for a merchant-requested input.", + "required": [ + "key", + "label", + "type", + "path" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "description": "Stable identifier for the field. Used in values object keys and in messages.path (e.g., $.additional_fields.values.po_number)." + }, + "label": { + "type": "string", + "description": "Buyer-facing label for the field." + }, + "description": { + "type": "string", + "description": "Additional context or instructions for the buyer." + }, + "type": { + "$ref": "#/$defs/input_type" + }, + "required": { + "type": "boolean", + "default": false, + "description": "If true, checkout remains incomplete until a value is provided." + }, + "default_value": { + "description": "Default value used when the platform does not provide buyer input.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, + "path": { + "type": "string", + "description": "RFC 9535 JSONPath to the target checkout data (e.g., buyer, cart totals, fulfillment option, or specific line items)." + }, + "validation_hints": { + "$ref": "#/$defs/validation_hints" + } + } + }, + "values": { + "type": "object", + "description": "Buyer-provided values keyed by additional field key.", + "propertyNames": { + "pattern": "^[A-Za-z0-9_-]+$" + }, + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + } + }, + "additional_fields": { + "type": "object", + "description": "Definitions provided by the business and values supplied by the platform.", + "properties": { + "fields": { + "type": "array", + "items": { + "$ref": "#/$defs/additional_field" + }, + "description": "Available fields and how they map into checkout data.", + "ucp_request": "omit" + }, + "values": { + "$ref": "#/$defs/values", + "ucp_request": { + "create": "optional", + "update": "optional", + "complete": "omit" + } + } + } + }, + "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": { + "$ref": "#/$defs/additional_fields", + "ucp_request": { + "create": "optional", + "update": "optional", + "complete": "omit" + } + } + } + } + ] + } + } +}