# Commerce App API Mesh

> Scaffold or update an Adobe API Mesh configuration (mesh.json) in front of a Commerce app: add GraphQL/OpenAPI sources, extend an existing Commerce GraphQL type with a new field, and wire a cross-source resolver for it. Use when the user mentions API Mesh, mesh.json, extending a Commerce GraphQL type (e.g. adding a field to Order/CustomerOrder/Product), or stitching a runtime action's data into the storefront's GraphQL schema.

- Skill: `adobe/commerce-app-api-mesh` (Agent Skill)
- Install (CLI): `npx skillmds@latest add adobe/commerce-app-api-mesh`
- Raw SKILL.md: https://api.skillmd.com/api/skills/adobe/commerce-app-api-mesh/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: Apache-2.0
- Author: Adobe (https://skillmd.com/u/adobe), verified publisher
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/adobe/commerce-app-api-mesh

---


# Wire API Mesh in Front of a Commerce App

Composes Commerce's own GraphQL API and this app's runtime actions into a single mesh schema. Two moves this skill covers: exposing a runtime action as a mesh source, and extending an existing Commerce type with a field resolved by delegating to that source.

This skill assumes general API Mesh knowledge (`mesh.json` anatomy, handler types, transforms, hooks, secrets, CORS, generic declarative/programmatic resolvers). If any of that is unfamiliar, load it from Adobe's own material first — see [References](#references) — rather than guessing at syntax. None of that material covers extending an existing Commerce type via `additionalResolvers` (`targetTypeName`/`sourceTypeName`/`requiredSelectionSet`/`sourceSelectionSet`) or wrapping an aio-commerce-sdk runtime action as a mesh source — that's what follows.

## Prerequisites

- `aio plugins install @adobe/aio-cli-plugin-api-mesh` is installed.
- If exposing a runtime action as a source, it's already built and deployed with a real, reachable HTTPS endpoint — a source pointing at an undeployed action fails opaquely.
- Check whether a mesh already exists for this workspace: `aio api-mesh:get`. "No mesh found" → you'll `create`; otherwise you're editing an existing `mesh.json` and will `update`.

## Step 1 — Confirm schema shapes via introspection

Before writing `additionalTypeDefs` or `additionalResolvers`, introspect the Commerce (or other) GraphQL source you're extending. Don't assume a type/field name from memory or a similar-sounding convention — near-miss names produce a mesh that builds successfully but whose resolver never fires.

```bash
curl -s -X POST "<graphql-endpoint>" -H "Content-Type: application/json" \
  -d '{"query":"{ __type(name: \"<TargetType>\") { fields { name } } }"}'
```

## Step 2 — Scaffold sources

```json
{
  "name": "Commerce",
  "handler": {
    "graphql": {
      "endpoint": "<commerce-graphql-endpoint>",
      "operationHeaders": { "Authorization": "{context.headers.authorization}" }
    }
  }
}
```

Include `operationHeaders` by default on any source whose schema has customer-, cart-, or session-scoped fields — API Mesh does **not** forward the caller's `Authorization` header automatically. Omitting it makes every authenticated query fail with the backend's own generic "not authorized" error, indistinguishable from an invalid token.

To wrap a runtime action, write a small static OpenAPI document describing just its endpoint and reference it by relative path:

```json
{
  "name": "<SourceName>",
  "handler": { "openapi": { "source": "./mesh/<source>.json" } }
}
```

The OpenAPI document itself needs enough shape for the mesh to generate a Query field from it — not just the pointer above. Minimal example for a single-endpoint runtime action:

```json
{
  "openapi": "3.0.0",
  "info": { "title": "<SourceName>", "version": "1.0.0" },
  "servers": [{ "url": "<runtime-action-base-url>" }],
  "paths": {
    "/<action-path>": {
      "get": {
        "operationId": "<sourceField>",
        "parameters": [
          {
            "name": "<arg>",
            "in": "query",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "<resultField>": { "type": "string" } }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

`operationId` becomes the Query field name — it must match `sourceFieldName` in Step 3's resolver exactly, or the resolver builds successfully but never fires.

The declared response schema must match what the action actually returns — the mesh parses according to what you declare, it doesn't reshape data.

## Step 3 — Extend a type and wire the resolver

```json
"additionalTypeDefs": "extend type <TargetType> { <newField>: String }",
"additionalResolvers": [
  {
    "targetTypeName": "<TargetType>",
    "targetFieldName": "<newField>",
    "sourceName": "<SourceName>",
    "sourceTypeName": "Query",
    "sourceFieldName": "<sourceField>",
    "requiredSelectionSet": "{ <keyField> }",
    "sourceArgs": { "<arg>": "{root.<keyField>}" },
    "sourceSelectionSet": "{ <resultField> }",
    "result": "<resultField>"
  }
]
```

Always pair `sourceSelectionSet` with `result` when extracting a scalar from an object-returning source field — never use `result` alone. The `result`-only path builds its selection set by hand instead of via the GraphQL parser, and breaks with `"No type was found for field node ... __typename"` specifically when the target field resolves inside a list (e.g. a parent's `items[].<newField>`). A direct root-query call to the same source field succeeds even when this bug is present, so that test alone isn't sufficient proof the resolver works.

## Step 4 — Deploy and verify

If you already know a browser-based app will call this mesh, decide `responseConfig.CORS` now, before your first deploy — the browser-verification tier below exists to catch a missed CORS config, but deciding upfront avoids a second deploy cycle.

The first `aio api-mesh:*` call in a session opens an interactive browser login (`Waiting for browser login...`). An agent without browser access can't complete this itself — hand the printed login URI to the human and wait.

```sh
aio api-mesh:create mesh.json -c   # first time
aio api-mesh:update mesh.json -c   # subsequent edits
```

`-c`/`--autoConfirmAction` skips the interactive `Are you sure you want to update the mesh: <id>?` prompt. That prompt is the only checkpoint before mutating a mesh other people or systems may already depend on — reserve `-c` for a workspace-scoped mesh you just created yourself (e.g. in CI, or a throwaway dev workspace). When updating an existing, shared, or already-deployed mesh, omit `-c` and have a human confirm the prompt, or at minimum get explicit human sign-off on the diff before running the command — treat this like any other live-infrastructure change, not a routine CLI call.

Provisioning is asynchronous — poll rather than assume completion:

```sh
until aio api-mesh:status 2>&1 | grep -qi success; do sleep 20; done
```

Verify in two tiers: first the source's root field directly, then the field in its real nested/authenticated shape (a list-nested query with a real caller credential, not a flat root-field call). Tier 1 passing does not prove tier 2 works — the bug above is invisible in tier 1.

If the consuming app will call this mesh directly from a browser (not just server-to-server), add a third tier: a real request from that app's actual origin. The two tiers above only prove server-side reachability — a mesh with no `responseConfig.CORS` entry for that origin passes both while still failing every browser call through it, not just the new field (see the CORS section of the api-mesh-starter-kit reference below).

## Common Issues

- **`"not authorized"` on an authenticated query, even with a valid token** — the source's `graphql` handler is missing `operationHeaders`. Check `mesh.json`, not the token.
- **`"No type was found for field node ... __typename"` on a nested/list field, but the source works fine at root** — the resolver uses `result` without `sourceSelectionSet`. Add it.

## Quality Bar

- `aio api-mesh:status` reports success, and the new field resolves correctly in its real nested/authenticated shape, not just at the source's root field.
- If a browser-based app will call this mesh, that app can complete a real request against it — not just `aio api-mesh:status` and `curl`.

## Chaining

- **The source doesn't exist yet as a runtime action** — invoke `commerce-app-storage`, `commerce-app-webhooks`, or `commerce-app-eventing` to scaffold and deploy it first.

## References

- [API Mesh prompting guide](https://developer.adobe.com/graphql-mesh-gateway/mesh/basic/prompting.md) — Adobe's own guidance for prompting an agent to write mesh configs; general workflow and expectations
- [api-mesh-starter-kit llm.txt](https://raw.githubusercontent.com/adobe-commerce/api-mesh-starter-kit/refs/heads/main/llm.txt) — reference knowledge base covering `mesh.json` anatomy, all three handler types, transforms, hooks, secrets, context state, CORS, and the CLI command set

