feat: add structured product attributes via schema.org PropertyValue#401
feat: add structured product attributes via schema.org PropertyValue#401sakinaroufid wants to merge 4 commits intoUniversal-Commerce-Protocol:mainfrom
Conversation
Add an optional `attributes` array on Product and Variant for measurable facts like battery life, display size, weight, and storage. Defined as a shared type at types/attribute.json with required name/value and optional key, numeric_value, and unit. Same shape as schema.org PropertyValue. The catalog spec doc gains an Attribute section with a placement decision tree (attributes vs options vs tags vs metadata), Product/Variant override semantics, and a worked example. Marked Working Draft until at least one merchant prototype lands. Additive: no new endpoints, transports, request shapes, or capability negotiation. Backward compatible with existing producers and consumers.
|
This is a welcome addition to the UCP spec. Structured product attributes via schema.org PropertyValue make it much easier for AI agents to do meaningful product comparison across merchants — an agent can filter on “processor generation” or “screen size” directly rather than parsing free-text descriptions. We are doing something similar on the BuyWhere side — normalizing product data from Shopee, Lazada, and other marketplaces into a structured schema with typed attributes before surfacing it through our MCP server. Having UCP standardize this at the protocol level would save a lot of per-source mapping work. One question: does this cover nested/complex attributes? For example, a laptop has CPU (with its own properties like cores, clock speed), RAM (type, size), and display (resolution, panel type). |
|
Great to see progress on structured product attributes. At BuyWhere (buywhere.ai), we have been working on normalized product catalogs across multiple merchants and regions. We use schema.org as a base but extend with commerce-specific properties (pricing tiers, availability windows, multi-currency). Happy to share our category taxonomy if it helps inform the UCP spec. |
|
Hi @ptiper - would you mind pls tagging the right reviewers? |
Description
Adds an optional
attributesarray onProductandVariantfor measurable facts like battery life, display size, weight, and storage. Shared type atsource/schemas/shopping/types/attribute.json, referenced from both schemas. Same shape as schema.org PropertyValue.Each entry has required
nameandvalueplus optionalkey,numeric_value, andunit.numeric_valuelets agents filter without parsing display strings.unitlets them compare across merchants.keygives verticals a stable slot for standardized identifiers without UCP shipping a canonical vocabulary on day one.Catalog spec doc gains an Attribute section: placement decision tree (
attributesvsoptionsvstagsvsmetadata), Product/Variant per-name override semantics, SHOULD-list of well-known units, worked example.Additive. No new endpoints, transports, request shapes, or capability negotiation. Existing producers and consumers are unaffected.
Why
Agents handling queries like "phone with at least 20 hours of battery, under $800" need measurable facts in structured form. The catalog response usually contains the answer but it's stuck in prose: "all-day battery", "lightweight at 1.2 kg".
tagsdoesn't carry units.metadataaccepts anything, so every merchant picks a different shape, which is the same fragmentation problem as parsing prose.optionsis for variant-defining axes (Color, Size), not measurable facts. None of them is a structured place for "Battery life: 22 hours."The shape matches schema.org PropertyValue, which is what most catalog teams already publish via JSON-LD on their product pages. For them, adoption is mostly piping existing data into UCP responses.
Design notes
Shared type, not inlined.
attributeslives on bothProductandVariant, which matches howcategory.json,media.json, andrating.jsonare referenced from both. Avoids drift between two parallel definitions.Variant.barcodesstays inlined because barcodes are variant-only.keyin addition toname. Schema.org PropertyValue has bothname(human-readable) andpropertyID(stable identifier). Using onlynamemakes cross-merchant filtering brittle: "Battery life" vs "battery_life" vs "Battery". The optionalkeyfield is thepropertyIDanalog. Producers can populate it now, verticals can standardize values later, consumers that only care aboutnameare unaffected.Per-name override, not whole-array replace. When the same attribute appears on both
ProductandVariant, Platforms MUST match bykey(if present) or case-insensitivename, override the Product entry with the Variant entry, and inherit any Product entries the Variant doesn't redeclare.Open vocabulary on
unit, with a SHOULD-list. Per the extensibility guidance: no enum. The schema description carries a SHOULD-list of well-known units (SI/UCUM where they exist:kg,mm,Hz; common catalog units otherwise:GB,inches,hours) plus anexamplesarray for codegen affinity. Same pattern asbarcodes.type.Out of scope for v1
nameorkeyvalues. Vertical extensions can standardize identifiers later.Category (Required)
ucp-schematool (resolver, linter, validator). (Requires Maintainer approval)Schema additions to
ProductandVariantqualify as Core Schema Modifications under the Contributing Guide, so this PR follows the Enhancement Proposal track. The proposal lives at #400.Related Issues
For #400. Currently in Proposal stage; this PR is the implementation reference and should not merge until the EP reaches Provisional via TC vote.
Checklist
generate_models.shunderpython_sdk. The Pydantic regeneration lives in thepython_sdkrepo and will land there as a follow-up once these schemas merge.Screenshots / Logs (if applicable)
Local checks against the configs in
.github/:ucp-schema lint source/: 80 files checked, all passed.DOCS_MODE=spec uv run mkdocs build --strict: built in 9.66s, no warnings or errors on the new section..github/linters/.markdownlint.jsonon the changed doc: 0 issues..cspell.jsonon changed files: 4 files, 0 issues.Test plan
name/valueenforced; each combination of optional fields validates; existing fixtures withoutattributesvalidate unchanged.namerejected; missingvaluerejected;numeric_valueas string rejected.numeric_valueabsent, onlyvaluepopulated).keyon Product and Variant, assert per-name merge).