angeo / module-ucp
Spec-compliant Universal Commerce Protocol (UCP) profile generator for Magento 2. Generates /.well-known/ucp at protocol version 2026-04-08 with ECDSA P-256 signing keys, declared capabilities, and proper cache headers. v0.1.x is profile-only — catalog, cart, checkout endpoints land in later releases.
Angeo UCP — /.well-known/ucp for Magento 2
Publishes a Universal Commerce Protocol business profile at
/.well-known/ucp so AI shopping agents (Google/Gemini, ChatGPT, etc.) can
discover your store's commerce capabilities.
- Multi-transport service bindings: REST out of the box, optional MCP
binding (point it atangeo/module-mcp-server's Streamable HTTP
endpoint) — extensible to further transports/services via
Api\ServiceBindingProviderInterfaceand the di.xml provider pool,
with the upcoming Lodging/Food verticals in mind - CI schema validation: profiles generated by the real module code are
validated against the official UCP JSON Schemas at the pinned spec tag
on every push and weekly (seedev/schema-validation/) - Profile generated per UCP spec 2026-04-08 (latest release) and
validated against the official JSON Schemas from the spec repository
(profile_schema.json/ucp.json#business_schema): the required
servicesandpayment_handlerskeys are always present, empty registries
serialize as{}, and admin-supplied payment handlers are validated
(id+versionrequired per entry) before publication - Served by a PHP controller — correct
Content-Type: application/json,
CORS, and cache headers, with no nginx/Apache changes - ECDSA P-256 signing keys, JWK-formatted, public keys only in the profile
How the endpoint is served
/.well-known/ucp is delivered by a controller, not a static file, because the
UCP spec requires Content-Type: application/json and CORS headers
(Access-Control-Allow-Origin: *) — neither of which a static file without an
extension can provide without editing the web server.
| Component | Role |
|---|---|
Controller\Router |
Custom router matching the exact path /.well-known/ucp and dispatching the action. Registered in etc/di.xml via RouterList (sortOrder 22, before the CMS router). Returns null for any other path. |
Controller\WellKnown\Ucp |
Builds and returns the profile as JSON with Content-Type: application/json, CORS, Cache-Control: public, max-age=300, and hardening headers. Answers OPTIONS preflight with 204 + CORS headers. Returns 404 when the module is disabled (the site simply does not advertise UCP). Public, no auth — as the spec requires. |
Model\ProfileGenerator |
Builds the spec-2026-04-08 profile (services, capabilities, extensions, payment handlers, supported versions, public signing keys). |
Model\Keys\KeyGenerator / JwkFormatter |
Generates ECDSA P-256 keys and formats the public half as a JWK. |
Why this reaches PHP without web-server changes
The stock Magento nginx config ends its main location with
try_files $uri $uri/ /index.php$is_args$args. A request for /.well-known/ucp
with no matching static file falls through to index.php, where the custom
router dispatches it. The official Magento nginx sample has no
location ~ /\. deny rule, so the dot-segment is not blocked.
If your host added a custom
location ~ /\. { deny all; }rule it blocks all
dot-paths, and the profile then needs a one-line nginx allow for
^~ /.well-known/. The stock config does not have this problem.
Do not leave a static file at
pub/.well-known/ucp: nginx would serve it
first (asapplication/octet-stream, no CORS) and the controller would never
run.
Install
composer require angeo/module-ucp
bin/magento module:enable Angeo_Ucp
bin/magento setup:upgrade
bin/magento cache:flush
Generate signing keys, then verify:
bin/magento angeo:ucp:keys:generate
curl -sI https://yourstore.com/.well-known/ucp
# HTTP/2 200
# content-type: application/json
# access-control-allow-origin: *
# cache-control: public, max-age=300
CLI
| Command | Purpose |
|---|---|
bin/magento angeo:ucp:keys:generate |
Generate / rotate the ECDSA P-256 signing key pair. |
bin/magento angeo:ucp:validate |
Validate the generated profile against the UCP spec. |
Configuration
Stores → Configuration → Angeo UCP — enable the module and declare which
capabilities your store supports (catalog search/lookup, cart, checkout, order,
identity linking, fulfillment, discount), payment handlers, and supported
protocol versions.
Security
- The profile is public and unauthenticated by design (per the UCP spec).
Never put secrets, internal URLs, or admin contacts in it. - Only public signing keys are published. The private key never leaves the
server;ProfileGeneratorreads public keys only (getPublicSigningKeys()).
SeeSECURITY.md. - The endpoint sends
X-Content-Type-Options: nosniff,X-Frame-Options: DENY,
andReferrer-Policy: no-referrer. - Rate-limiting is the operator's responsibility (reverse proxy / WAF).
- If a CDN/Varnish fronts the site, the
Cache-Controlheader lets it cache the
profile; purge/.well-known/ucpafter rotating keys or changing config.
Changelog
All notable changes to angeo/module-ucp are documented here.
Format follows Keep a Changelog.
Versioning follows Semantic Versioning.
[1.4.0] - 2026-07-04
Extensibility release: multi-transport service bindings and automated
schema-drift detection. Spec version 2026-04-08 remains the latest UCP
release; the official roadmap signals Lodging and Food verticals next, so
the services registry is now open for extension without core changes.
Added
- MCP transport binding. New admin field Transport → MCP Endpoint URL
(angeo_ucp/transport/mcp_endpoint). When set, the profile advertises an
additionalmcpbinding fordev.ucp.shoppingalongside REST — per
schemas/service.json, each transport binding is a separate entry in the
same service array, andmcp(likerest) REQUIRES anendpointin
business profiles. The MCP endpoint is opt-in only and never derived from
the store base URL: an MCP server (e.g.angeo/module-mcp-server's
Streamable HTTP endpoint) is separate infrastructure. Api\ServiceBindingProviderInterface(@api) + DI provider pool.
ProfileGeneratorno longer hardcodes the services registry; it merges
registry fragments from a di.xml-configured pool
(serviceBindingProviders). Built-in providers:
Model\Service\RestShoppingBindingProvider(extracted, behavior
unchanged) andModel\Service\McpShoppingBindingProvider. Third-party
modules — a future catalog implementation, additional transports, or the
upcoming Lodging/Food verticals — contribute bindings via their own
di.xml without touching this module. Malformed fragments
(non-reverse-domain keys, non-list values) are dropped defensively.- CI schema validation (
.github/workflows/schema-validation.yml+
dev/schema-validation/). Fixture profiles are generated through the
real module code (framework interfaces stubbed, no Magento install
needed) and validated against the OFFICIAL JSON Schemas at the pinned
spec tag on every push/PR and weekly. When the next dated spec release
lands, bumpingUCP_SPEC_TAGimmediately reveals any schema drift.
Fixtures cover: full profile (REST + MCP), minimal, MCP-only, and the
orphan-extension pruning case.
Changed
Config::getRestEndpoint()/getMcpEndpoint()trim surrounding
whitespace from configured values, preventing a whitespace-only value
from being advertised as an endpoint.dev.ucp.shoppingservice name centralised as
Config::SHOPPING_SERVICE_NAME.
Upgrade notes
ProfileGenerator::__construct()gained a second argument
(array $serviceBindingProviders = []). Wired automatically via di.xml;
only code instantiating the class directly (e.g. tests) needs updating.- Behavior is unchanged for existing installs: with no MCP endpoint
configured, the emitted profile is byte-identical.
Schema-compliance release. The generated profile is now validated end-to-end
against the official JSON Schemas from the spec repository
(Universal-Commerce-Protocol/ucp, tag v2026-04-08:
source/discovery/profile_schema.json, source/schemas/ucp.json,
service.json, capability.json, payment_handler.json). Spec version
2026-04-08 remains the latest UCP release as of July 2026.
Fixed
ucp.servicesanducp.payment_handlerswere omitted when empty —
schema violation.ucp.json#/$defs/business_schemamarks BOTH keys as
REQUIRED in a business profile, even when empty. Previously
payment_handlerswas dropped when not configured andserviceswas
emitted as an empty value only implicitly. Both keys (pluscapabilities)
are now always present.- Empty registries JSON-encoded as
[]instead of{}— type violation.
services,capabilities, andpayment_handlersare JSON objects keyed
by reverse-domain name. Empty PHP arrays encode as JSON arrays, so a store
with no capabilities served a profile that failed schema validation
([] is not of type 'object'). Empty registries are now emitted as
stdClassand serialize as{}. - Admin-supplied
payment_handlerswere passed through unvalidated.
Perpayment_handler.json#/$defs/base, every handler entry REQUIRES a
stringidand aYYYY-MM-DDversion; the registry must be keyed by
reverse-domain names with array values.Config::getPaymentHandlers()now
enforces all of this, skipping invalid keys/entries with logged warnings so
the served profile always validates. A single entry object is tolerated and
wrapped into the required array form.
Added
- CORS preflight support. The
/.well-known/ucpaction now also
implementsHttpOptionsActionInterfaceand answersOPTIONSwith
204 No ContentplusAllow,Access-Control-Allow-*, and
Access-Control-Max-Ageheaders. Previously Magento's HTTP-method
validation rejected OPTIONS even though the response advertised
Access-Control-Allow-Methods: GET, OPTIONS. The preflight responds
identically whether or not the profile is enabled, so it does not leak
the advertised state. - Controller unit tests covering the OPTIONS preflight, the disabled
404 (no-store), the 200 response headers required by the spec's hosting
rules (Content-Type: application/json, CORS,Cache-Control: public
withmax-age >= 60), and the 500 error path. - Admin warning on Identity Linking. The
identity_linking_enabled
field now documents that declaring this capability requires publishing
OAuth 2.0 authorization server metadata at
/.well-known/oauth-authorization-server(RFC 8414), which is outside
this module's scope.
Changed
angeo:ucp:validaterewritten against the official schemas. New
checks: presence of the requiredservices/payment_handlerskeys;
reverse-domain pattern for all registry keys;endpointREQUIRED for
rest/mcp/a2a service bindings in business profiles; payment handler
id/versionrequirements;kid/ktyREQUIRED on signing keys.
Relaxed to match the business schema: capabilityspec/schemaare now
warnings (optional at business level; onlyversionis REQUIRED), and an
emptyservicesregistry is a warning rather than an error (schema-valid,
but agents have nothing to call).- Reverse-domain validation uses the exact pattern from the spec's
reverse_domain_name.jsontype (Config::REVERSE_DOMAIN_PATTERN).
Verified (no change needed)
- Protocol version
2026-04-08is still the latest UCP release. signing_keyscorrectly published as a top-level sibling ofucp
(profile_schema.json#/$defs/base).- Catalog correctly split into
catalog.search+catalog.lookup;
extensions correctly pruned when orphaned;identity_linkingcorrectly
underdev.ucp.commonwithconfig.scopesas an object map; ES256 /
P-256 JWK signing keys match the Signatures spec (RFC 9421 foundation).
[1.2.0] - 2026-06-13
Fixes the endpoint wiring so /.well-known/ucp is actually served by the
controller, and removes a conflicting static-file code path.
Fixed
- Router registered in the wrong area — endpoint always 404'd. The
RouterListentry for the custom router was placed in the global
etc/di.xml. The frontend "standard"RouterListis frontend-scoped, so a
global registration is not reliably applied to it: the router'smatch()
never ran and every request fell through to the CMS router (HTTP 404,
text/html, with Magento page-cache tags). The registration is now in
etc/frontend/di.xml, which actually wires the router into frontend routing.
This was the underlying cause behind the earlier 404 reports. - 404 on LiteSpeed / Hostinger (and other non-nginx servers). The router
matched onlygetPathInfo()with a strict comparison. On LiteSpeed the
PATH_INFOfor a dot-segment path is frequently empty, so the comparison
failed and the request fell through to Magento's CMS router (HTTP 404,
text/html). The router now checksgetPathInfo(),getOriginalPathInfo()
andgetRequestUri(), strips the query string, and normalises slashes, so it
matches/.well-known/ucpregardless of how the web server populates the path. - Missing DI preference for
ProfileGeneratorInterface. The controller and
theangeo:ucp:validatecommand depend on the interface, but no
<preference>bound it toModel\ProfileGenerator, sosetup:upgradefailed
with "Cannot instantiate interface Angeo\Ucp\Api\ProfileGeneratorInterface".
This was latent until the router was wired up (nothing previously
instantiated the controller). The preference is now declared inetc/di.xml. - Custom router was never registered.
Controller\Routerexisted but no
RouterListentry referenced it (and there was noetc/frontend/routes.xml),
so the controller was dead code. As a result/.well-known/ucpeither
returned 404, or — when a static file was present — was served by the web
server asapplication/octet-streamwith no CORS headers, failing UCP
validation. The router is now registered inetc/di.xmlviaRouterList
(sortOrder 22) and aetc/frontend/routes.xmldeclares the route, so the
controller serves the endpoint with the correct headers. - Missing CORS headers. The controller now sends
Access-Control-Allow-Origin: *andAccess-Control-Allow-Methods: GET, OPTIONS
on every response, as required for cross-origin fetches by AI agents.
Removed
- Static-file approach deleted.
Service\WellKnownWriter,
Service\UcpProfileBuilder, and theangeo:ucp:generatecommand
(Console\Command\GenerateWellKnown) were removed. Writing a static
pub/.well-known/ucpconflicted with the controller — the web server served
the static file first, with the wrong Content-Type — and the builder was a
stub duplicate ofModel\ProfileGenerator. The controller is now the single
source of truth for the endpoint. - Removed a duplicate
di.xmlfrom the module root (Magento only reads
etc/di.xml).
Housekeeping
- Removed macOS
__MACOSXresource-fork artifacts from the package.
[1.1.0] - 2026-06-12
Full review against the live UCP 2026-04-08 specification at
ucp.dev. One
spec-compliance defect was found and fixed; the profile generator now covers
every business-profile feature defined by the spec.
Fixed — spec compliance
- [SPEC] Catalog capability naming corrected. The spec defines catalog as
TWO granular capabilities —dev.ucp.shopping.catalog.searchand
dev.ucp.shopping.catalog.lookup— with distinct spec pages
(/specification/catalog/search,/specification/catalog/lookup) and
schemas (catalog_search.json,catalog_lookup.json). The previous
monolithicdev.ucp.shopping.catalogcapability does not exist in the spec
and would not match any platform's declared capability during the
intersection algorithm, silently disabling catalog for all AI agents.
Backwards compatible: the legacycatalog_enabledconfig value is still
honoured and enables both granular capabilities.
Added — full business-profile coverage
- Extensions with
extendsdeclarations per spec:dev.ucp.shopping.fulfillment(extendsdev.ucp.shopping.checkout)dev.ucp.shopping.discount(extends checkout and/or cart; emits a string
for a single parent and an array for multiple parents, per spec)
- Orphaned-extension pruning. Extensions are never advertised when no
parent capability is enabled — the spec prunes orphans during negotiation,
so advertising one is always dead weight. The generator enforces this and
the validator reports it as an error. - Identity-linking OAuth scopes. New admin field publishes
config.scopes(e.g.dev.ucp.shopping.order:read) under
dev.ucp.common.identity_linking; scope values encode as empty JSON
objects{}exactly as the spec example shows. supported_versions(spec SHOULD for businesses supporting older
protocol versions): admin JSON field, validated — keys must be YYYY-MM-DD,
URIs must be HTTPS; invalid entries are skipped with a logged warning.payment_handlersdeclaration: admin JSON field, validated to be a
JSON object keyed by reverse-domain handler name; arrays and malformed
JSON are rejected with a logged warning. Documentation warns that profile
contents are public and must not contain secret keys.- Validator upgraded to a spec-conformance checker:
- capability required fields (
version,spec,schema— spec: REQUIRED) - service-binding required fields (
version,spec,transport) - Spec URL Binding:
dev.ucp.*capabilities must point at
https://ucp.dev/origins (spec: MUST; platforms reject mismatches) - orphaned-extension detection
- trailing-slash warning on endpoints (spec: SHOULD NOT)
supported_versionsformat checks- private-key-material detection in published
signing_keys
- capability required fields (
Changed
- README rewritten with an explicit protocol-coverage matrix documenting
which UCP layers this module implements (discovery) and which require
separate endpoint implementations (negotiation, transactional REST
endpoints, RFC 9421 response signatures, payment handler execution). - Admin capability section renamed to "Declared Root Capabilities" with a
new "Declared Extensions" section; comments clarify that enabling a
capability advertises it but does not implement the endpoint. module.xmlsetup_version and composer version bumped to 1.1.0.
Notes — protocol coverage
This module deliberately implements the discovery layer of UCP
(business profile + signing keys + hosting rules). The transactional flow —
capability negotiation, catalog/cart/checkout/order endpoints, RFC 9421
message signatures, payment execution — is the responsibility of endpoint
modules that this profile advertises. Enable capability toggles only when
the matching endpoint implementation is installed.
[1.0.0] - 2026-06-11
First production-ready release. All items below are security fixes or
reliability improvements over the 0.1.x beta line.
Security
-
[SECURITY] HTTP endpoint validation in
Config::getRestEndpoint().
Configured and auto-derived endpoint URLs are now checked for the
https://scheme. Awarningis emitted tosystem.logif a
non-HTTPS URL is detected, alerting the operator before AI agents
reject the profile. The UCP spec requires HTTPS for/.well-known/ucp. -
[SECURITY] Over-length coordinate rejection in
JwkFormatter.
Coordinates longer than 32 bytes (the P-256 fixed width) are now
rejected with aRuntimeExceptioninstead of being silently included.
Previously only short coordinates were left-padded; an over-long value
from a malformed key would have produced an invalid JWK without error. -
[SECURITY] Security response headers added to all controller responses.
X-Content-Type-Options: nosniff,X-Frame-Options: DENY, and
Referrer-Policy: no-referrerare now set on every response from the
UCP controller (200, 404, and 500). This aligns with standard Magento
hardening practices. -
[SECURITY] JWK entry validation before returning public keys.
Config::getPublicSigningKeys()now validates that each entry in the
stored JWK array contains the required fields (kid,kty,crv,
x,y) before including it in the profile. Malformed entries are
skipped and awarningis logged. -
[SECURITY] Empty
kidrejection inJwkFormatter.
publicKeyToJwk()now throws if$kidis an empty string, preventing
a JWK with a blank key identifier from being emitted.
Fixed
-
GenerateKeysCommandscope mismatch.
The existing-key check now reads atSCOPE_TYPE_DEFAULT(matching the
scope used byConfigWriter::saveConfig()). Previously reading at
store scope could return an empty string even when a key was already
stored at default scope, allowing the command to silently overwrite it. -
GenerateKeysCommandusesinvalidate()instead ofcleanType().
TypeListInterface::invalidate('config')marks the config cache as
dirty without flushing unrelated cache types.cleanType()is a
heavier operation that is unnecessary here. -
ValidateProfileCommandHTTPS check.
The validator now explicitly checks that every declared service-binding
endpoint URL useshttps://. Previously a profile with anhttp://
endpoint would pass validation even though the UCP spec forbids it. -
ValidateProfileCommandseverity adjustment.
The "no capabilities and no signing keys" condition is now awarning
rather than a hardFAILURE. A discovery-only deployment that has
no capabilities yet is structurally valid; operators are warned but
the command exits0so CI pipelines are not broken during initial
setup. -
KeyGeneratorusesextension_loaded()instead offunction_exists()
to check for OpenSSL.function_exists('openssl_pkey_new')is true
even when the extension is partially initialised on some platforms;
extension_loaded('openssl')is the canonical check. -
KeyGeneratorkid entropy increased from 2 to 4 bytes.
4 bytes of CSPRNG output (8 hex characters, ~4 billion distinct values)
makes accidental kid collisions negligible even in automated
high-rotation environments. -
module.xmlnow declaressetup_version="1.0.0".
Removed
extra.branch-aliasremoved fromcomposer.json; no longer needed
for a stable tagged release.
[0.1.1-beta] - 2026-05-23
Fixed
- Removed the static
"version"field fromcomposer.json. Packagist
now reads the version from the git tag, which is the canonical
Composer practice and eliminates the risk of the manifest version
drifting from the released tag.
Changed
- Installation now requires the explicit
@betastability qualifier:
composer require angeo/module-ucp:^0.1@beta. - README updated: roadmap row, install instructions, and a note that
0.1.0is yanked. extra.branch-aliasmappingdev-mainto0.1.x-devadded.
[0.1.0-beta] - 2026-05-23
Fixed
- Critical:
composer.jsonwas invalid JSON (trailing comma after
support.source), preventingcomposer requirefrom succeeding. magento/module-storeis now explicitly required incomposer.json
and listed inmodule.xml<sequence>.- Removed dead
etc/frontend/routes.xml. - Removed unused imports in
Controller/WellKnown/Ucp.php.
Changed
Angeo\Ucp\Model\Confignow takes aPsr\Log\LoggerInterfaceconstructor argument.Controller\WellKnown\Ucpnow catches non-JsonExceptionthrowables.
Added
SECURITY.md,ext-opensslandext-jsoncomposer requirements.- Two new
ConfigTestcases covering logging behaviour.
[0.1.0] - 2026-05-21 (yanked — composer.json was invalid JSON)
Added
- Initial public release.
| Version | Stability | QA Status | Compatibility | Released |
|---|---|---|---|---|
| 1.4.0 | stable | Fail | Magento 2.4.7-2.4.9 Details | 2026-07-29 19:18:51 |
| 1.2.0 | stable | Fail | Magento 2.4.7-2.4.9 Details | 2026-06-14 18:53:11 |
| 0.1.1-beta | beta | Fail | Magento 2.4.7-2.4.9 Details | 2026-05-23 17:53:53 |
| 0.1.0-beta | beta | Not tested | Not yet tested Details | 2026-05-23 17:28:26 |
Requires 7
| Package | Constraint |
|---|---|
| ext-json | * |
| ext-openssl | * |
| magento/framework | >=103.0.0 |
| magento/module-backend | >=102.0.0 |
| magento/module-config | >=101.0.0 |
| magento/module-store | >=101.0.0 |
| php | >=8.2 |
Requires-dev 3
| Package | Constraint |
|---|---|
| magento/magento-coding-standard | * |
| phpstan/phpstan | ^1.10 |
| phpunit/phpunit | ^10.0 |
Compatibility
Each Magento release line is installed on its supported PHP versions, then the module is built (DI compilation + static-content deploy) and its unit and integration suites are run. The matrix shows the lines and PHP versions the module is confirmed to install and run on. Code-quality results further down (phpstan, phpcs, …) are reported separately and never affect compatibility.
Code Quality
Advisory checks against the module's source. Static analysis runs once across the whole module; PHPStan re-runs per Magento + PHP version because resolvable symbols differ between releases. These NEVER affect the Compatibility badge. A phpcs finding can't make a module incompatible.
Static analysis
Coding standards (phpcs), mess detection (phpmd), copy-pasted code (cpd), PHP cross-version compatibility, composer.json validity. Each runs once for the whole module.
Tests
Unit and integration suites, run for each applicable Magento and PHP version. A test failure speaks to the module's behaviour, not its compatibility with a Magento line, so it is reported here separately and never reddens the compatibility matrix.
Unit tests
Integration tests
| Magento | PHP 8.2 | PHP 8.3 | PHP 8.4 | PHP 8.5 |
|---|---|---|---|---|
| 2.4.7 | N/A | N/A | ||
| 2.4.8 | N/A | N/A | ||
| 2.4.9 | N/A | N/A |
Security
Security checks run directly against the module: an audit of its declared dependencies for known vulnerabilities (composer audit) and a scan of its source for malware and web-shell signatures. Each runs once. A malware detection fails the version outright.
More from angeo
View vendorMagento 2 module for AI Engine Optimization (AEO). Generates spec-compliant llms.txt and llms-full.txt per llmstxt.org standard, plus streaming JSONL for vector indexing. Multi-store, multi-website, CLI, cron, async admin UI, Page Builder-aware sanitization, customer-group pricing, atomic writes, ETag/Cache-Control, .md mirrors.
Magento 2 module for AI Engine Optimization (AEO). Injects AI crawler rules (OAI-SearchBot, GPTBot, ChatGPT-User, PerplexityBot, Perplexity-User, Google-Extended, ClaudeBot, anthropic-ai, Claude-User, Applebot, cohere-ai, Amazonbot, Meta-ExternalAgent) into robots.txt — without overwriting your existing configuration. Supports per-bot Allow/Disallow lists, Crawl-delay, Sitemap directives, multi-store, and a public Api\RobotsStatusInterface for cross-module integration with angeo/module-aeo-audit.
Live AI brand visibility audit for Magento 2. Queries ChatGPT, Claude, Perplexity, Gemini and Groq with brand-probing prompts and scores real-world AI recall, citation rate and recommendation presence. Extends angeo/module-aeo-audit v3 via CheckerInterface as the 16th signal, alongside the 15 built-in technical checks.
Magento 2 AEO (AI Engine Optimization) Audit. v3 covers 15 signals — robots.txt AI bots, llms.txt + llms.jsonl, Product / Organization / FAQ schema, merchant return + shipping policies, sitemap.xml, UCP profile, AI product feed, OG tags, canonical + hreflang, JSON-LD quality, well-known endpoint matrix, Core Web Vitals via CrUX. Score Trend dashboard, Admin UI, cron, dynamic fix commands, dependency-injected extension point for custom checkers.
Turn an existing module into recurring revenue.
If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.