Module reference

UCP Catalog for Magento 2 — module reference

Covers angeo/module-ucp-catalog 2.1.1 · Checked 16 October 2026 · MIT licensed

UCP Catalog is the part that answers. UCP Profile tells agents that your store supports catalog search and lookup. This module serves those calls, following UCP spec 2026-08-25, so an agent that finds your store through /.well-known/ucp can actually shop it.

Endpoints

Method and pathOperationCapability
POST /ucp/v1/catalog/searchsearch_catalogdev.ucp.shopping.catalog.search
POST /ucp/v1/catalog/lookuplookup_catalogdev.ucp.shopping.catalog.lookup
POST /ucp/v1/catalog/productget_productdev.ucp.shopping.catalog.lookup

Responses are checked in CI against the official UCP JSON Schemas.

Requirements

Magento2.4.6+ (Open Source, Mage-OS or Adobe Commerce) — magento/framework >=103.0.6 <104
PHP8.1 – 8.5 — ~8.1.0||~8.2.0||~8.3.0||~8.4.0||~8.5.0
Other angeo modulesangeo/module-ucp ^2.1
PHP extensionsjson, mbstring

Magento_ConfigurableProduct is suggested, not required. Without it, products fall back to one variant each.

Installation

composer require angeo/module-ucp-catalog
bin/magento module:enable Angeo_UcpCatalog
bin/magento setup:upgrade
bin/magento setup:di:compile                # production mode only
bin/magento cache:flush

Then, in Stores → Configuration → Angeo → UCP (Universal Commerce Protocol):

  1. Turn on the profile and declare Catalog Search and Catalog Lookup.
  2. Leave Transport → REST Endpoint URL empty, or set it to https://yourstore.com/ucp/v1, so the profile advertises the paths this module serves.
  3. Run bin/magento angeo:ucp:validate.

This module has no settings of its own.

Variants

In UCP a product has variants, and the variant is what you buy: it has its own price, stock and options. A Magento configurable product has the same shape, and the module maps it that way:

Classic Shirt                       price_range 29.00 – 32.50 EUR
├─ SHIRT-BLUE-S   29.00  in_stock       Color: Blue, Size: Small
├─ SHIRT-BLUE-L   32.50  in_stock       Color: Blue, Size: Large
└─ SHIRT-RED-S    29.00  out_of_stock   Color: Red,  Size: Small

Narrowing options with get_product

POST /catalog/product is where option selection happens. preferences lists option names in the order they may be relaxed — the server drops options from the end of the list first.

curl -s -X POST https://yourstore.com/ucp/v1/catalog/product \
  -H 'Content-Type: application/json' \
  -d '{
        "id": "gid://magento/Product/10",
        "selected": [{"name":"Color","label":"Red"},{"name":"Size","label":"Large"}],
        "preferences": ["Color", "Size"]
      }'

Red/Large does not exist above, so Size is relaxed and SHIRT-RED-S comes back, with a message that says what changed. Send ["Size","Color"] and you get SHIRT-BLUE-L.

Each option value says if it exists and if it is available:

existsavailable
Red / Smalltruefalse — sold, but out of stock
Red / Largefalsefalse — never made

These are different facts. Mixing them up is how an agent tells a shopper something is “unavailable” when it never existed.

Filters

  • **price** — min and max in ISO 4217 minor units, in context.currency. If that currency differs from the store’s and no conversion exists, the filter is skipped and the response says so.
  • **categories** — OR logic, matched without case against the labels in product.categories.

signals and context.intent are accepted and ignored. Acting on them means relevance ranking, and a LIKE query dressed up as ranking would be worse than an honest search.

Smoke test

curl -s -X POST https://yourstore.com/ucp/v1/catalog/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"shirt","pagination":{"limit":5},"filters":{"price":{"max":15000}}}' | python3 -m json.tool

curl -s -X POST https://yourstore.com/ucp/v1/catalog/lookup \
  -H 'Content-Type: application/json' \
  -d '{"ids":["YOUR-SKU"]}' | python3 -m json.tool

Expect ucp.version = 2026-08-25, ucp.status = success, products[] with price_range in minor units, and pagination.total_count. With the module or capability off you get a spec-shaped 404 with ucp.status = error.

Request signatures (2.1.0)

All three endpoints check RFC 9421 signatures, controlled by Request Security → Inbound Signature Verification in the UCP Profile settings. The check runs before any catalog work. Rejections return 401 with a spec-shaped error. The default is disabled, so an upgrade changes nothing until you turn it on.

Not done yet

Signing responses (Signature, Signature-Input, Content-Digest). The optional policies and actions response members of 2026-08-25 are not sent either.

FAQ

Do I need UCP Profile as well?

Yes. The profile tells agents the endpoints exist, and this module requires it. Both move together, because the endpoints read the protocol version from the profile module.

Are configurable products supported?

Yes. Each child becomes a variant with its own price, stock and option values, and the parent shows the real price range.

Why is my price filter ignored?

The filter uses minor units in the request currency. If that currency is not one the store can convert to, the filter is skipped and the response includes a message saying so.

Does it rank results by relevance?

No. It runs Magento’s catalog search and is honest about it. Signals and intent are accepted but not used for ranking.

Is any setup needed in this module?

No. Turn on Catalog Search and Catalog Lookup in the UCP Profile settings and check with angeo:ucp:validate.

This page was written from the README.md, composer.json and etc/*.xml files of tag 2.1.1, read on 16 October 2026. Settings, commands and defaults come straight from that code. If a newer release is out, the README on GitHub wins.