The fulfillment extension enables businesses to advertise support for physical goods fulfillment (shipping, pickup, etc).
This extension adds a fulfillment field to Checkout containing:
methods[]— fulfillment methods applicable to cart items (shipping, pickup, etc.)line_item_ids— which items this method fulfillsdestinations[]— where to fulfill (address, store location)groups[]— business-generated packages, each with selectableoptions[]
available_methods[]— inventory availability per item (optional)
Mental model:
methods[0]Shippingline_item_ids👕👖selected_destination_id=destinations[0].id🔘✅ 123 Fake Stgroups[0]📦👕👖selected_option_id=options[0].id🔘✅ Standard $5options[1]🔘 Express $10
methods[1]Pick Up in Storeline_item_ids👞selected_destination_id=destinations[0].id🔘✅ Uptown Storegroups[0]📦👞selected_option_id=options[0].id🔘✅ In-Store Pickupoptions[1]🔘 Curbside Pickup
Fulfillment applies only to items requiring physical delivery. Items not requiring fulfillment (e.g., digital goods) do not need to be assigned to a method.
{{ extension_fields('fulfillment', 'fulfillment') }}
{{ schema_fields('types/fulfillment_resp', 'fulfillment') }}
{{ schema_fields('types/fulfillment_method_resp', 'fulfillment') }}
{{ schema_fields('types/fulfillment_destination_resp', 'fulfillment') }}
{{ schema_fields('types/shipping_destination_resp', 'fulfillment') }}
{{ schema_fields('types/retail_location_resp', 'fulfillment') }}
{{ schema_fields('types/fulfillment_group_resp', 'fulfillment') }}
{{ schema_fields('types/fulfillment_option_resp', 'fulfillment') }}
{{ schema_fields('types/fulfillment_available_method_resp', 'fulfillment') }}
{{ schema_fields('types/total_line_resp', 'fulfillment') }}
{{ schema_fields('postal_address', 'fulfillment') }}
{
"fulfillment": {
"methods": [
{
"id": "method_1",
"type": "shipping",
"line_item_ids": ["shirt", "pants"],
"selected_destination_id": "dest_1",
"destinations": [
{
"id": "dest_1",
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
"postal_code": "62701",
"address_country": "US"
}
],
"groups": [
{
"id": "package_1",
"line_item_ids": ["shirt", "pants"],
"selected_option_id": "standard",
"options": [
{
"id": "standard",
"title": "Standard Shipping",
"description": "Arrives Dec 12-15 via USPS",
"totals": [
{
"type": "total",
"amount": 500
}
]
},
{
"id": "express",
"title": "Express Shipping",
"description": "Arrives Dec 10-11 via FedEx",
"totals": [
{
"type": "total",
"amount": 1000
}
]
}
]
}
]
}
]
}
}Fulfillment options are designed for method-agnostic rendering. Platforms do not need to understand specific method types (shipping, pickup, etc.) to present options meaningfully. The business provides precomputed, human-readable fields that platforms render directly.
| Location | Field | Required | Purpose |
|---|---|---|---|
groups[].options[] |
title |
Yes | Primary label that distinguishes from siblings |
groups[].options[] |
description |
No | Supplementary context for the title |
groups[].options[] |
total |
Yes | Price in minor units (may be null if not yet available) |
available_methods[] |
description |
No | Standalone explanation of alternative availability |
For options[].title:
- MUST distinguish this option from its siblings
- SHOULD include method and speed (e.g., "Express Shipping", "Curbside Pickup")
- MUST be sufficient for buyer decision if
descriptionis absent
For options[].description:
- MUST NOT repeat
titleortotal—provides supplementary context only - SHOULD include timing, carrier, or other decision-relevant details
- SHOULD be a complete phrase (e.g., "Arrives Dec 12-15 via FedEx")
- MAY be omitted if title is self-explanatory
For available_methods[].description:
- MUST be a standalone sentence explaining what, when, and where
- SHOULD be usable verbatim in platform dialogue (e.g., "Pants available for pickup at Downtown Store today at 2pm")
For ordering:
- Businesses SHOULD return
options[]in a meaningful order (e.g., cheapest first, fastest first) - Platforms SHOULD render options in the provided order
Platforms SHOULD treat fulfillment as a generic, renderable structure:
- Render each option as a card using
title,description, andtotal - Present options in the order provided by the business
- Present all methods returned—method selection is a buyer decision
- Use
available_methods[].descriptionto surface alternatives to the buyer
Platforms MAY provide enhanced UX for recognized method types (store
selectors
for pickup, carrier logos for shipping), but this is optional. The baseline
contract is: title + description + total is sufficient to render any
option.
When a buyer selects an option the platform cannot fully process, the
platform SHOULD use continue_url to hand off to the business's checkout.
Available methods indicate whether an item can be fulfilled with a given method, and when. Use cases:
- Alternative methods: "These pants are also available for pickup at Downtown Store"
- Fulfill later: Preorders, items shipping from a distant warehouse, pickup when store gets inventory
{
"fulfillment": {
"methods": [
{
"id": "shipping",
"type": "shipping",
"line_item_ids": ["shirt", "pants"]
},
{
"id": "pickup",
"type": "pickup",
"line_item_ids": []
}
],
"available_methods": [
{
"type": "shipping",
"line_item_ids": ["shirt", "pants"],
"fulfillable_on": "now"
},
{
"type": "pickup",
"line_item_ids": ["pants"],
"fulfillable_on": "2026-12-01T10:00:00Z",
"description": "Available for pickup at Downtown Store today at 2pm"
}
]
}
}The description field enables platforms to surface alternatives to buyers:
🤖 The shirt and pants ship for $5, arriving in 5-8 days. Or the pants can be picked up at Downtown Store in 4 hours.
If the buyer chooses pickup but the platform doesn't support split
fulfillment, the platform SHOULD use continue_url to hand off to the
business's checkout.
Businesses and platforms declare fulfillment constraints in their profiles. Businesses fetch platform profiles to adapt responses accordingly.
Platforms declare their rendering capabilities using platform_schema:
{{ schema_fields('types/platform_fulfillment_config', 'fulfillment') }}
Platforms that omit config or set supports_multi_group: false receive
single-group responses. The response shape is always
methods[].groups[]—the difference is whether groups.length can exceed 1
within each method.
// Default: single group per method
{ "dev.ucp.shopping.fulfillment": [{"version": "{{ ucp_version }}"}] }
// Opt-in: business MAY return multiple groups per method
{ "dev.ucp.shopping.fulfillment": [{"version": "{{ ucp_version }}", "config": { "supports_multi_group": true }}] }Businesses declare what fulfillment configurations they support using
merchant_config:
{{ schema_fields('types/merchant_fulfillment_config', 'fulfillment') }}
{
"capabilities": {
"dev.ucp.shopping.fulfillment": [
{
"version": "{{ ucp_version }}",
"config": {
"allows_multi_destination": {
"shipping": true
},
"allows_method_combinations": [["shipping", "pickup"]]
}
}
]
}
}This example says: shipping can go to multiple addresses, and carts can mix shipping+pickup.
When supports_multi_group: false (default):
- Business MUST consolidate all items into a single group per method
- Response still uses array structure:
methods[].groups[]withgroups.length === 1 - Business MAY still return multiple methods (e.g., shipping + pickup) if cart items require it
When supports_multi_group: true:
- Business MAY return multiple groups per method based on inventory, packaging, or warehouse logic
- Platform is responsible for rendering group selection UI (e.g., choose shipping speed per package)
Extensions that extend fulfillment with new method types (e.g.,
local_delivery) MUST add an extension schema that:
- Adds the method to the
typeenum infulfillment_method - Adds corresponding business config options:
allows_multi_destination.local_delivery: booleanallows_method_combinationsitems enum (includes"local_delivery")
Note: Platform's supports_multi_group is method-agnostic (single boolean),
so no extension needed.
Config: None required (default behavior)
{
"fulfillment": {
"methods": [
{
"id": "method_1",
"type": "shipping",
"line_item_ids": ["shirt", "pants"],
"selected_destination_id": "dest_1",
"destinations": [
{
"id": "dest_1",
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
"postal_code": "62701",
"address_country": "US"
}
],
"groups": [
{
"id": "package_1",
"line_item_ids": ["shirt", "pants"],
"selected_option_id": "standard",
"options": [
{
"id": "standard",
"title": "Standard Shipping",
"description": "Arrives Dec 12-15 via USPS",
"totals": [
{
"type": "total",
"amount": 500
}
]
},
{
"id": "express",
"title": "Express Shipping",
"description": "Arrives Dec 10-11 via FedEx",
"totals": [
{
"type": "total",
"amount": 1000
}
]
}
]
}
]
}
]
}
}Config: Platform profile requires config.supports_multi_group: true
Business splits items into multiple packages; buyer selects shipping rate per package.
{
"fulfillment": {
"methods": [
{
"id": "method_1",
"type": "shipping",
"line_item_ids": ["shirt", "pants"],
"selected_destination_id": "dest_1",
"destinations": [
{
"id": "dest_1",
"street_address": "123 Main St",
"address_locality": "Springfield",
"address_region": "IL",
"postal_code": "62701",
"address_country": "US"
}
],
"groups": [
{
"id": "package_1",
"line_item_ids": ["shirt"],
"selected_option_id": "standard",
"options": [
{
"id": "standard",
"title": "Standard",
"totals": [ {"type": "total", "amount": 500} ]
},
{
"id": "express",
"title": "Express",
"totals": [ {"type": "total", "amount": 1000} ]
}
]
},
{
"id": "package_2",
"line_item_ids": ["pants"],
"selected_option_id": "express",
"options": [
{
"id": "standard",
"title": "Standard",
"totals": [ {"type": "total", "amount": 500} ]
},
{
"id": "express",
"title": "Express",
"totals": [ {"type": "total", "amount": 1000} ]
}
]
}
]
}
]
}
}Config: Business profile requires
config.allows_multi_destination.shipping: true
Shirt ships to mom (US), pants ship to grandma (Hong Kong). Two methods of the same type, each with its own destination.
{
"fulfillment": {
"methods": [
{
"id": "method_1",
"type": "shipping",
"line_item_ids": ["shirt"],
"selected_destination_id": "dest_mom",
"destinations": [
{
"id": "dest_mom",
"street_address": "123 Mom St",
"address_locality": "Springfield",
"address_region": "IL",
"postal_code": "62701",
"address_country": "US"
}
],
"groups": [
{
"id": "package_1",
"line_item_ids": ["shirt"],
"selected_option_id": "standard",
"options": [
{
"id": "standard",
"title": "Standard",
"totals": [
{
"type": "total",
"amount": 500
}
]
},
{
"id": "express",
"title": "Express",
"totals": [
{
"type": "total",
"amount": 1000
}
]
}
]
}
]
},
{
"id": "method_2",
"type": "shipping",
"line_item_ids": ["pants"],
"selected_destination_id": "dest_grandma",
"destinations": [
{
"id": "dest_grandma",
"street_address": "88 Queensway",
"address_locality": "Hong Kong",
"address_country": "HK"
}
],
"groups": [
{
"id": "package_2",
"line_item_ids": ["pants"],
"selected_option_id": "standard",
"options": [
{
"id": "standard",
"title": "Standard",
"totals": [
{
"type": "total",
"amount": 500
}
]
},
{
"id": "express",
"title": "Express",
"totals": [
{
"type": "total",
"amount": 1000
}
]
}
]
}
]
}
]
}
}