tcgunel / ucp-laravel
Universal Commerce Protocol (UCP) for Laravel — expose your storefront to AI shopping agents.
Requires
- php: ^8.2
- illuminate/contracts: ^11.0 || ^12.0 || ^13.0
- illuminate/http: ^11.0 || ^12.0 || ^13.0
- illuminate/routing: ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.20
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- phpunit/phpunit: ^11.0 || ^12.0
README
Expose any Laravel storefront to AI shopping agents through Google's Universal Commerce Protocol (UCP).
The Universal Commerce Protocol is the open standard — announced by Google in January 2026 and developed with Shopify, Etsy, Wayfair, Target and Walmart — that lets AI agents (Google AI Mode, Gemini, and others) discover and buy from a store programmatically.
ucp-laravel makes your application UCP-native. You implement small per-capability
contracts that map your products and orders onto the UCP schema; the package serves the
discovery manifest, the capability endpoints, validation, and the response envelopes.
Capabilities
The package implements the full UCP 2026-04-08 shopping capability surface over
the REST binding:
| Capability | UCP identifier | Status |
|---|---|---|
| Discovery manifest | /.well-known/ucp |
✅ |
| Catalog — search / lookup / detail | dev.ucp.shopping.catalog |
✅ |
| Cart | dev.ucp.shopping.cart |
✅ |
| Checkout | dev.ucp.shopping.checkout |
✅ |
| Discount | dev.ucp.shopping.discount |
✅ |
| Fulfillment | dev.ucp.shopping.fulfillment |
✅ |
| Order — lookup + webhooks | dev.ucp.shopping.order |
✅ |
| Identity Linking — OAuth 2.0 | dev.ucp.common.identity_linking |
✅ |
| AP2 Mandates — agent payments | dev.ucp.shopping.ap2_mandate |
✅ |
Security-critical pieces stay in your application, behind contracts: the package
never captures payment, never implements an OAuth server, and never owns
your signing keys. Checkout completion, the OAuth /oauth2/* endpoints and the ES256
crypto are delegated to provider contracts you implement.
Spec note. UCP is young and its schemas are still moving. This package mirrors the
2026-04-08revision; treat the JSON shapes as a faithful implementation, not a frozen guarantee. Verify against ucp.dev before going to production.
Requirements
- PHP 8.2+
- Laravel 11, 12 or 13
Installation
Once published to Packagist:
composer require tcgunel/ucp-laravel
While the package is consumed privately across projects, add it as a path or VCS repository — see docs/installation.md.
Publish the config file (optional — the package works with sensible defaults):
php artisan vendor:publish --tag=ucp-config
The service provider is auto-discovered. The UCP routes are live immediately; each capability stays dormant — and unadvertised in the manifest — until you configure its provider.
Quick start
Each capability is one contract. Start with Catalog — it is read-only and the fastest to ship:
namespace App\Ucp; use App\Models\Product as Listing; use Tcgunel\Ucp\Contracts\CatalogProvider; use Tcgunel\Ucp\DTO\Availability; use Tcgunel\Ucp\DTO\Money; use Tcgunel\Ucp\DTO\PriceRange; use Tcgunel\Ucp\DTO\Product; use Tcgunel\Ucp\DTO\RequestContext; use Tcgunel\Ucp\DTO\SearchQuery; use Tcgunel\Ucp\DTO\SearchResult; use Tcgunel\Ucp\DTO\Variant; final class EloquentCatalogProvider implements CatalogProvider { public function search(SearchQuery $query): SearchResult { $listings = Listing::query() ->when($query->hasQuery(), fn ($q) => $q->where('name', 'like', "%{$query->query}%")) ->limit($query->pagination->limit) ->get(); return new SearchResult(products: $listings->map($this->toUcpProduct(...))->all()); } public function lookup(array $ids, RequestContext $context): array { return Listing::query()->findMany($ids)->map($this->toUcpProduct(...))->all(); } public function product(string $id, array $selected, array $preferences, RequestContext $context): ?Product { $listing = Listing::query()->find($id); return $listing instanceof Listing ? $this->toUcpProduct($listing) : null; } private function toUcpProduct(Listing $listing): Product { $price = new Money((int) $listing->price_in_kurus, 'TRY'); return new Product( id: (string) $listing->id, title: $listing->name, description: $listing->description ?? '', priceRange: PriceRange::uniform($price), variants: [ new Variant( id: (string) $listing->id, title: $listing->name, price: $price, availability: $listing->stock > 0 ? Availability::inStock($listing->stock) : Availability::outOfStock(), ), ], ); } }
Register it:
// config/ucp.php 'catalog_provider' => \App\Ucp\EloquentCatalogProvider::class,
Every other capability follows the same shape — implement a contract, register it under its config key. See the guides below.
Endpoints
| Method & path | Capability |
|---|---|
GET /.well-known/ucp |
discovery manifest |
GET /.well-known/oauth-authorization-server · /.well-known/oauth-protected-resource |
Identity Linking |
POST /catalog/search · /catalog/lookup · /catalog/product |
Catalog |
POST /carts · GET·PUT /carts/{id} · POST /carts/{id}/cancel |
Cart |
POST /checkout-sessions · GET·PUT /checkout-sessions/{id} · POST …/complete · …/cancel |
Checkout |
GET /orders/{id} |
Order |
Catalog requests reject malformed input with 422. For cart, checkout and order,
business outcomes return 200 with a messages[] array — the UCP convention —
while only protocol-level problems use HTTP error statuses. Order-lifecycle updates are
pushed to agents as signed webhooks.
How it works
AI shopping agent
│
├── GET /.well-known/ucp ──────────► ManifestController ──► ManifestBuilder
│ (advertises only configured capabilities)
│
├── GET /.well-known/oauth-* ───────► IdentityController ──► IdentityLinkProvider
│
└── POST /catalog · /carts · ───────► capability controller
/checkout-sessions · GET /orders (validate → DTO)
│
▼
Catalog · Cart · Checkout · Order providers ← YOUR CODE
The package owns the protocol — routing, the manifest, request validation, the ucp
response envelope, the JSON schema. Your application owns the data, the OAuth server and
the crypto, through small contracts. Controllers stay thin; DTOs are immutable; every
capability is an interface.
Documentation
| Guide | Contents |
|---|---|
| Installation | Composer setup, publishing, what gets registered |
| Configuration | Every config/ucp.php key |
| Catalog provider | The catalog contract, the DTOs, money precision |
| Cart & checkout providers | The session contracts, lifecycle, discounts, fulfilment, payment |
| Order, Identity & AP2 | Order webhooks, OAuth discovery, AP2 mandates |
| Multi-tenancy | Per-tenant manifests and capabilities |
| Manifest reference | The /.well-known/ucp JSON structure |
| Roadmap | What's next |
Multi-tenancy
The package is built for multi-tenant SaaS. Endpoint URLs in the manifest follow the request host, so per-tenant domains work with zero extra wiring. See docs/multi-tenancy.md.
Testing
composer test
The suite runs against Orchestra Testbench; no host application is required.
Code quality
| Tool | Command | Standard |
|---|---|---|
| Pint | composer lint / composer format |
Laravel preset + strict types |
| PHPStan (Larastan) | composer analyse |
Level 8 |
| PHPUnit | composer test |
— |
composer check runs all three.
Contributing
Bug reports and pull requests are welcome. Please run composer check before opening a
PR. See CHANGELOG.md for release history.
License
The MIT License. See LICENSE.
Credits
- Built by Tolga Can Günel.
- Implements the Universal Commerce Protocol — a project of Google and the broader commerce community.