Build, test, deploy, install, and certify production-ready commercetools Connect applications — service/API-extension, event/subscription, job, and merchant-center custom apps — in TypeScript, JavaScript, or Java, and integrate a deployed connector into a custom storefront. Covers the connect.yaml contract, least-privilege scopes, lifecycle scripts, sync-vs-async idempotency/ack, testing, and deployment. Includes connector sub-areas for payment (Stripe, Adyen), tax (Avalara, Vertex), PIM (Akeneo), CRM (Salesforce, HubSpot), order-management/OMS, gift cards, transactional email (SendGrid), marketplace (Mirakl), promotion and loyalty (Talon.One, Voucherify), analytics export to a warehouse/CDP (BigQuery, Segment), search/product discovery (Algolia), and shipping (carriers, rate engines). Use when building, configuring, forking, or debugging a commercetools Connect connector, or syncing commercetools data to or from an external system. Not for the hosted Checkout widget (see commercetools-checkout).
Intent-driven guidance for building production-ready Connect applications. This skill teaches the decision frameworks, platform contracts, and best practices that survive a production-readiness review — not a single connector's code. It generalizes patterns (and warns against anti-patterns) found in real connectors, and grounds every platform fact in official docs.
Language scope: Connect applications can be written in JavaScript/TypeScript or Java (docs); the create-connect-app template supports JS and TS. This skill targets TypeScript/Node — the decision frameworks, platform contracts (timeouts, ack semantics, scopes, lifecycle), and connect.yaml guidance are language-agnostic and apply equally to a Java connector, but the code snippets and the supertest + msw test stack are Node/Express-specific.
Tooling — use the Connect CLI, don't hand-roll. Scaffold, run, and ship with the official Connect CLI (@commercetools/cli). Every CLI command, the bootstrap flow, and the pinned dependency versions live in one place: the Connect CLI reference (connect-cli.md). Merchant Center custom applications/views are the exception: they use a separate frontend toolchain (@commercetools-frontend/*) and only ride the Connect CLI at deploy time and directory structure — see merchant-center-cli.md and merchant-center-customizations.md.
Workflow
When this skill is invoked, always follow these steps:
Docs search (required, run first) — Always begin by searching docs for this skill. This is the mandatory grounding step: it gathers the latest verified documentation as context for you (the agent). Do not skip it, and do not replace it with another tool (such as an MCP documentation-search tool) This script optimizes for tuned search results — run this command:
Use its output as your primary grounding. You may additionally use the commercetools Knowledge MCP or https://docs.commercetools.com/connect for deeper follow-up.
Route with the decision framework (below) — Pick the application type and lock in the sync-vs-async contract before writing code. The contract determines almost every later decision.
Open the matching reference(s) in ./references/ and build to their patterns and ## Checklist.
Gate on the production-readiness checklist (below) before declaring the connector done.
Optional scripts
Fetch GraphQL schema — Run this when you need context about a commercetools GraphQL query or mutation — for example, to inspect a resource's fields, types, and available operations before writing a query, or to verify a GraphQL query/mutation you have just generated against the real schema. It fetches the partial GraphQL SDL for a single commercetools resource:
The output is the GraphQL SDL for that resource. If the resource name is not recognized, the script prints the list of valid resource names — pick the correct one and re-run. Note: the SDL may contain stubbed types — referenced resources rendered as stubs, with their real type name given in a comment. Fetch any you need separately by re-running this script with that type name as --resource-name.
Fetch OpenAPI (REST) schema — Run this when you need context about a commercetools REST endpoint, request/response payload, or update action — for example, to inspect a resource's REST operations before constructing a request, or to verify a REST request/payload you have just generated against the real specification. It fetches the partial OpenAPI specification for a single commercetools resource:
The output is the OpenAPI specification (YAML) for that resource. REST resources use a read/write-split naming form (e.g. api-Cart-read, api-Cart-write). If the resource name is not recognized, the script prints the list of valid resource names — pick the correct one and re-run. Note: the spec does not include reference-expansion schemas — fetch a referenced resource's schema separately by re-running this script with that resource as --resource-name.
Step 1 — Decision framework: which application type?
A Connector is one repository declaring one or more applications in connect.yaml. Pick each application's type by how your code is invoked and which way data flows, not by what it does.
Two things to fix first:
Direction. Is commercetools the source of the change (commercetools → external system), or is the external system the source (external system → commercetools)? Both are common; they route differently.
service is just an HTTP endpoint, not necessarily an API Extension. A service app exposes an HTTP endpoint. That endpoint can be registered as an API Extension (commercetools calls it synchronously inside an operation) or be a plain inbound webhook / REST API that an external system calls to push data in. These are two modes with different contracts.
Trigger / need
Type
How your code is invoked
Hard contract
Block or modify a commercetools operation before it persists (validate a cart, inject tax, reject an order)
service as API Extension
commercetools calls your endpoint synchronously during the API request (registered as an Extension)
Extension response limit: 2 s default, 10 s self-service max (per-project increases available via support request, subject to performance review). Your latency and downtime become the platform's.
An external system pushes data into commercetools as it changes (system A updates a product → upsert it into commercetools)
service as inbound webhook / API
the external system calls your endpoint
5-min service request timeout. You authenticate the caller and call the commercetools API yourself; no Extension is registered.
React to a commercetools change after it happened (sync a confirmed order to a WMS, send an email, index a product)
event (Subscription handler)
commercetools delivers a Subscription message to a queue → your handler
At-least-once, no ordering, redelivery on non-ack. Must be idempotent.
Scheduled or on-demand batch (nightly poll an external system and upsert, reconcile, cleanup, bulk import)
job
a cron scheduler (properties.schedule)
Request times out after 30 min. No concurrency guard — you own locking.
A single connector commonly combines types (e.g. a service API Extension that calculates tax on the cart plus an event handler that commits the transaction when the order is placed; or a service inbound webhook for live pushes plus a job for nightly full reconciliation).
The build-side guidance in this skill is connector-type-agnostic (any service/event/job). Some connector types also have a focused, end-to-end sub-area that owns the whole job for that type — from "is there a connector already?" through configuring, forking, or building one, to the application backend around it:
The full payment lifecycle for a custom storefront: decide whether a certified/public connector fits → configure it, or fork it, or spin up a new one from the payment-integration template → build the backend (session BFF, Order after authorization, capture/refund/cancel via the processor, webhook reconciliation); plus debugging the round trip
The full tax integration: decide whether a certified connector fits (Avalara/Vertex have them; TaxJar does not) → configure it, fork it, or build from the tax-integration template → the two apps (a cart API Extension that calculates tax in ExternalAmount mode + an OrderCreated Subscription that records/commits the transaction); plus the sandbox-doesn't-persist and no-nexus-means-zero traps
The full customer-relationship integration: decide whether a public connector fits (classic CRMs usually have none → build) → configure it, fork it, or build for a CRM you define → pick direction + source of truth first, then the customer-sync apps it implies (event syncers out, an inbound webhook/poll in, a one-time migration job), all linked by externalId; plus the duplicate-contact, sync-loop, and PII/deletion traps
The full product-data sync job: decide whether a public PIM connector fits → configure it, or fork it, or build one → map the PIM model onto Product Types/attributes/categories/media, keep price & inventory separate, and pick the sync architecture (Import API vs HTTP API, event webhook vs job)
Order management (OMS, e.g Fluent Commerce, kbrw, OneStock, NewStore, Pipe17)
Connect commercetools to an OMS: decide whether to install a public connector → configure it, or fork/customize one, or build a new one for a bespoke order-management service (scaffold from the fulfilment-integration template) → design the sync (order export on OrderCreated, status/shipment/fulfillment inbound webhook, inventory sync, reconcile job). No fixed connector contract; composes the type-agnostic event/service/job build-side
Gift card (e.g Voucherify, in-house store credit, ...etc)
The full gift card integration: decide whether to use a public connector directly (Voucherify), customize/fork one, or build a new one from the gift-card template for a gift card system you define → the two apps (an enabler UI + a processor that checks balance, redeems value, and owns the Payment via session-authenticated balance/redeem and Payment Intents refund/reverse); plus the must-pair-with-a-fallback and sample-only-simulates traps
The full transactional email integration: decide whether a ready-made connector fits (email is template-first — most ESPs have none) → configure it, fork/customize it, or build the one event app from the transactional email template → the app (a Subscription on Customer/Order Messages → send via the ESP); the central at-most-once vs at-least-once decision for a non-idempotent send; plus the token-email, order-state-filtering, and localization traps
Marketplace — multi-vendor, or selling on an external marketplace (e.g Marketplacer, Mirakl, Convictional, channel managers, ...etc)
The full marketplace integration: fix the role (operator vs selling on someone else's marketplace) and direction per domain → ask the user whether to use a public connector directly, customise/fork one, or build for a service they define (most marketplace listings are partner integrations, and there is no marketplace template) → model sellers and offers (Channel/Store/CustomObject per seller, per-seller prices + inventory, one Product for a shared SKU) → build the sync apps (seller + offer sync, order import or per-seller routing with syncInfo, fulfilment status, reconciliation); plus the channel-less-price, aggregated-availability, and un-deletable-Channel traps
The full promotion integration: first rule out native Cart Discounts/Discount Codes/Discount Groups (rung 0) → then ask the user whether to use a public connector as-is, customise/fork one, or build one for a promotion service they define (there is no promotion template) → the two apps (a cart API Extension that applies the engine's discounts via setDirectDiscounts + an OrderCreated Subscription that redeems and awards points); plus the Direct-Discounts-make-Discount-Codes-inert rule and the double-redemption and abandoned-cart traps
Analytics — export to a data warehouse / CDP / product-analytics tool (e.g BigQuery, Snowflake, Redshift, Databricks, Segment, mParticle, ...etc)
The full analytics egress: there is no turnkey analytics connector and no Export API, so (after the live registry check) build from the product-export template → a directional egress pipeline of two primitives, an event streamer on Subscriptions/Messages (near-real-time) and/or a job querying the API with lastModifiedAt windowing + cursor pagination (batch/backfill) → the event→row transform and destination-side dedup on resource.id+sequenceNumber; plus the disambiguation from Platform Insights (APM) and Change History (governance), the client-side-tracking boundary, and the duplicate-row/missing-event/payloadNotIncluded traps
The full search integration (outbound, backend-only — no API Extension): first rule out native Product Search / Product Projection Search (rung 0) → then use a public connector, fork one, or scaffold from the product-export template → map a Product Projection onto a flat search document (price-context, locales, category denormalization, Store assortment) → the two apps (a full-ingestion service/job that atomically reindexes the catalog + an incremental-updater event on ProductPublished/ProductUnpublished/store-selection Subscriptions); plus the ghost-record, half-empty-rebuild, and eventual-consistency traps, and the vendor-hosted-integration-is-not-a-connector rule
The full shipping integration: first rule out native Shipping Methods (rung 0) — zones, tiered rates over cart score, predicates, and the freeze + setCustomShippingMethod pattern cover most "dynamic shipping" asks → then ask the user whether to use a public connector (search integrationTypes=shipping), customise/fork one, or build for a shipping service they define (there is no Checkout shipping connector contract and no shipping template, and most vendors ship a vendor-hosted integration) → then the landing decision everything hangs off: setShippingRateInput over native tiers vssetCustomShippingMethod/addCustomShippingMethod, which never appears in matching-cart
Start at the matching overview.md for any payment-, tax-, CRM-, PIM-, order-management-, gift-card-, email-, marketplace-, promotion-, analytics-, search-, or shipping-connector task — integrating a deployed one or building/forking one. Each decision ladder routes you: rung 1 configure, rung 2 config-closes-the-gap, rung 3 fork, rung 4 build-from-template (provider gotchas live in the provider file — payment/stripe.md, tax/avalara.md, email/providers.md; the CRM and PIM sub-areas are vendor-neutral — look the connector up live; the OMS sub-area has no fixed connector contract and composes the build-side directly; the gift-card sub-area has one public connector (Voucherify) plus a build-from-template path for an in-house system; the marketplace sub-area is vendor-neutral and has no template — assess any fork candidate from its current repo; the promotion sub-area has two public MIT integrations and no template — promotion/public-connectors.md names which artifact is actually the production one; the analytics sub-area has no turnkey connector and no Export API — it still forces a live registry check, then builds a directional egress pipeline from the product-export template — analytics/destinations.md routes the warehouse/CDP/product-analytics/BI decision; the search sub-area is vendor-neutral, gates on native Product Search first, and scaffolds the outbound build from the product-export template; the shipping sub-area is vendor-neutral, gates on native Shipping Methods first, and has no Checkout connector contract and no template — shippingis a valid IntegrationType to search the registry on, but nothing prescribes the connector's shape, so it scaffolds from tax-integration and/or fulfilment-integration). It hands back to the build-side workflow and references above only for the deep, type-agnostic publish/certify lifecycle and the production-readiness gate.
The promotion, search, and shipping sub-areas each have a rung 0. commercetools ships its own discount engine (Cart Discounts, Discount Codes, Discount Groups), its own search (Product Search / Product Projection Search), and its own shipping model (Zones, Shipping Methods, tiered rates over shippingRateInput, predicates), so "should this be a connector at all?" is a real question in those three in a way it isn't for payment, tax, or the rest — rule the native capability out explicitly before recommending a connector.
Each sub-area lives under references/integrations/<type>/ with its own overview.md. Adding another connector type later means adding a sibling references/integrations/<type>/ tree and one row here — the build-side guidance does not change.
Marketplace listings are not all Connect connectors — verify before recommending
Whenever a sub-area has you check the commercetools marketplace for an existing connector, apply this rule regardless of connector type or vendor:
The marketplace is fine as a discovery source, but it lists integrations that are not necessarily commercetools Connect connectors — partner-operated services, SaaS products, and iPaaS middleware appear alongside deployable Connect applications. It can also be out of sync with the actual Connect connector registry (a listing may exist for something not deployable via Connect, or the version may differ), and any specific vendor (Akeneo, Stripe, …) may or may not be listed at any given time — never assume a named connector exists.
Double-check that a candidate is actually a commercetools Connect connector before recommending it as install/configure/fork: look for a Connect affordance (a public connector repo / connect.yaml / a Connect deploy action), and treat the Connect CLI / connector registry as authoritative over the marketing listing.
Then ask the user what to do — don't silently pick. Present the fit and whether it's Connect-deployable.
If the user wants to use a non–Connect integration, warn that this skill does not cover using non–Connect connectors — its build/configure/deploy patterns (connect.yaml, the Connect CLI, lifecycle scripts, the Connect deployment model) don't apply. Point them to the vendor/partner's own onboarding, and offer the in-skill alternative: build or fork a Connect connector instead.
Open every sub-area with the paths — don't wait to be asked
When a request routes into any sub-area above, the user has told you what they want to integrate. They have not told you how, and it is not yours to assume. So before requirements gathering, before config, before code — lay out the paths and let the user pick one:
Native first, where a rung 0 exists (promotion, search). If commercetools already ships the capability, say so plainly and stop. Don't design around something the platform does.
Deploy an existing public connector as-is — grounded in a live registry/marketplace check, never memory, and verified Connect-deployable per the rule above.
Fork and modify an existing connector — when there's a real gap that configuration can't close.
Build a new one — from the sub-area's template, or from the type-agnostic service/event/job patterns when no template exists.
State which rung you'd recommend and why, then ask the user to choose. These are materially different amounts of work and the decision is theirs, not yours.
Do this unprompted, in your first substantive response in the sub-area — including (especially) when the user's phrasing already sounds like it presumes an answer. "Build me an X integration", "sync Y into commercetools", or naming a service they're already running are not instructions to skip the ladder: a user who says "build" usually means "make this work" and will happily take an install if one exists. Ask a clarifying question or two first if you genuinely can't fit-check without it, but don't let requirements gathering delay the landscape — present what exists early, then let the requirements decide the rung.
Each sub-area's overview.md carries the full ordered gate (its Step 1.4/1.5) with the fit criteria and the template to build from. This is the rule that governs all of them.
Step 2 — Price the contract before you build
The expensive mistakes come from not pricing the contract you just chose:
service as API Extension couples your availability and latency to the commercetools operation. A slow or down extension makes carts and orders slow or impossible. So: a tight outbound timeout under the extension timeout, a deliberate fail-open vs. fail-closed decision, and minimizing work on the hot path (skip redundant external calls).
service as inbound webhook is not coupled to a commercetools operation (the 5-min service timeout applies, not the 2 s extension limit), but you own everything: authenticate the caller, validate the payload, and make the write idempotent (the same product update may arrive twice) — upsert by key, don't blind-create. Decide what a failed write returns so the caller can retry safely.
Asynchronous (event) trades immediacy for resilience but hands you at-least-once delivery, no ordering, and redelivery. So: idempotency keyed on a stable identifier, redelivery-safe acks (2xx for "don't send again"), re-fetch the resource by ID rather than trusting a possibly-stale or omitted payload, and self-change filtering to avoid loops.
job owns its own scheduling headroom, overlap locking, and restart-safe checkpointing; each unit of work must be idempotent so a re-run or overlap can't double-write.
If you cannot articulate, in one sentence each, your latency budget (extension), your idempotency strategy (inbound webhook / event / job), and your fail/retry behavior, you are not ready to write the handler.
Production-readiness checklist (the gate)
A connector is not done until every applicable item holds. Each maps to a reference with the implementation pattern.
Reliability
Idempotency strategy stated and implemented — statelessly. Reprocessing a message is a no-op via the target system's own idempotency, re-fetching the commercetools resource and re-checking its state, or upsert by a stable key — never a local dedup store. → event-applications.md
Redelivery-safe responses. Event endpoints return a positive ack (102/200/201/202/204) for handled and irrelevant-but-acked messages; anything other than 102, 200, 201, 202, or 204 triggers a retry. → event-applications.md
Re-fetch by ID, don't trust the payload. Handlers fetch the current resource by resource.id; required when payloadNotIncluded is set. → event-applications.md
Hot-path work minimized (sync). Extensions skip the external call when relevant data is unchanged (e.g. a stored hash) and short-circuit early. → service-applications.md
Security
Inbound endpoints authenticated. Service extensions register a destination whose authentication.type is the discriminator value AuthorizationHeader — not the schema's type name AuthorizationHeaderAuthentication, which fails with InvalidJsonInput — (or AzureFunctions) and validate that secret in-app. Webhooks from external systems validate a full JWT (signature, issuer, audience, subject, expiry, algorithm). A postDeploy that hits this typo may not surface as a failed deployment, so confirm the Extension actually registered via GET /{projectKey}/extensions. → security.md, service-applications.md Pattern 1
Least-privilege CT scopes. Use inheritAs.apiClient.scopes with only the scopes the apps need (e.g. manage_orders, manage_subscriptions, manage_extensions) — not an admin/manage_project client. → security.md
Secrets in securedConfiguration. API keys, client secrets, JWT secrets are never standardConfiguration and never hardcoded. → security.md
No stack traces or secrets in responses. Error middleware returns a generic message in production. → security.md
Correctness
Envelope validation. Envelope decoded per the injected destination type — branch on CONNECT_SUBSCRIPTION_DESTINATION (Pub/Sub: message.data is base64; SNS has its own envelope) — then validated (→ JSON → resource ref → notificationType) before any processing; malformed envelopes rejected. → event-applications.md
Message-type filtering. Subscribe to only the needed message types; ack-and-ignore anything else (including the platform's test/subscription messages). → event-applications.md
Self-change filtering. Updates your own connector makes don't re-trigger it into a loop. → event-applications.md
Route path matches connect.yamlendpoint. The Express router is mounted at the same base path as the app's endpoint (e.g. endpoint: /service ↔ app.use('/service', router)), or the platform's traffic 404s. → project-structure.md
Pinned SDK + client versions. JS/TS: @commercetools/platform-sdk@^8 + @commercetools/ts-client@^4 (not the legacy @commercetools/sdk-client-v2). Java: spring-boot-starter-parent 3.5.15+ and commercetools Java SDK 19+. Typed end to end, no any escapes, mapped at the boundary. → connect-cli.md (Step 3).
Observability
Structured logs with correlation IDs. JSON logs carry the message/resource correlation key (X-Correlation-ID for extensions, resource.id + sequenceNumber for events) on every log line for a request. → observability-operations.md
Idempotent lifecycle scripts.postDeploy creates resources get-then-update (create only if absent), never blind delete-then-recreate. preUndeploy cleans them up. → lifecycle-scripts.md
Deploy-time dependency validation.postDeploy test-connects to external services and surfaces invalid credentials immediately. → lifecycle-scripts.md
Fail-open vs fail-closed documented. The README states, per use case, what happens when the external dependency is down, and outbound calls have a timeout budget. → service-applications.md
Poison-message / replay runbook. How a repeatedly-failing message is handled (DLQ / dropped after retention) and how to replay. → observability-operations.md
Quality
Tests cover the real behavior, run via commercetools connect application test. At minimum: the parameterized auth-rejection matrix (missing/expired/wrong-issuer/wrong-audience/alg:none), envelope/ack edge cases (event) or the pure business logic + response actions (service), an idempotency/duplicate-delivery test, and idempotent postDeploy registration. A couple of happy-path tests is not enough. → testing.md
No dead code, no any escapes. No commented-out blocks; SDK types preserved end to end. → project-structure.md
Scaffolded and run with the Connect CLI. Project created via commercetools connect init; commercetools connect validate passes. → connect-cli.md (Step 2)
Generated connector docs
The connector ships a README stating its fail-open/fail-closed stance, required scopes, a configuration table (every connect.yaml key), and the poison-message/replay runbook. → deployment-installation.md
Custom application vs custom view; config-file contract; develop/test locally; deploy via Connect (connect.yaml merchant-center-* types, order of operations)
Integrating a deployed payment connector (sub-area)
Start at the overview; it routes to the rest (integrate, configure, fork, or build a new one). See also the Connector-type integration sub-areas section above.
Concern
Reference
Start here — the backend-focused workflow: requirements → is-a-certified-connector-enough → config → BFF/Order/capture-refund/webhook
Integrating or building a tax connector (sub-area)
Start at the overview; it routes to the rest (configure a certified connector, fork one, or build both apps from the template). See also the Connector-type integration sub-areas section above.
Concern
Reference
Start here — the two-app workflow: requirements → is-a-certified-connector-enough → config → calculate + record
The two-app contract: the calculator API Extension (all four tax actions, 200-not-202, fail modes, call reduction) + the order-syncer Subscription (commit/void/refund, idempotency); full pitfall catalog
Avalara ground truth (from the certified open-source connector): exact keys, AvaTax createTransaction quote-vs-commit, tax-code/entity-use mapping, MC config app — plus TaxJar as the build-from-template contrast
Integrating or building a CRM connector (sub-area)
Start at the overview; it routes to the rest (configure a public connector, fork one, or build for a CRM you define). See also the Connector-type integration sub-areas section above.
Concern
Reference
Start here — the sync workflow: requirements → direction + source of truth → is-a-public-connector-enough → config → build the sync apps
Requirements → connect.yaml: direction → app composition, source of truth, externalId/Custom-Field linking, least-privilege scopes, secured config; worked example
Verify the round trip: record linked by externalId
…(truncated)
1---2name: commercetools-connect3description: Build, test, deploy, install, and certify production-ready commercetools Connect applications — service/API-extension, event/subscription, job, and merchant-center custom apps — in TypeScript, JavaScript, or Java, and integrate a deployed connector into a custom storefront. Covers the connect.yaml contract, least-privilege scopes, lifecycle scripts, sync-vs-async idempotency/ack, testing, and deployment. Includes connector sub-areas for payment (Stripe, Adyen), tax (Avalara, Vertex), PIM (Akeneo), CRM (Salesforce, HubSpot), order-management/OMS, gift cards, transactional email (SendGrid), marketplace (Mirakl), promotion and loyalty (Talon.One, Voucherify), analytics export to a warehouse/CDP (BigQuery, Segment), search/product discovery (Algolia), and shipping (carriers, rate engines). Use when building, configuring, forking, or debugging a commercetools Connect connector, or syncing commercetools data to or from an external system. Not for the hosted Checkout widget (see commercetools-checkout).4---56# commercetools Connect78Intent-driven guidance for building **production-ready** Connect applications. This skill teaches the decision frameworks, platform contracts, and best practices that survive a production-readiness review — not a single connector's code. It generalizes patterns (and warns against anti-patterns) found in real connectors, and grounds every platform fact in official docs.910**Language scope:** Connect applications can be written in **JavaScript/TypeScript or Java** ([docs](https://docs.commercetools.com/connect/development.md)); the `create-connect-app` template supports JS and TS. **This skill targets TypeScript/Node** — the decision frameworks, platform contracts (timeouts, ack semantics, scopes, lifecycle), and `connect.yaml` guidance are language-agnostic and apply equally to a Java connector, but the code snippets and the supertest + msw test stack are Node/Express-specific.1112**Tooling — use the Connect CLI, don't hand-roll.** Scaffold, run, and ship with the official Connect CLI (`@commercetools/cli`). Every CLI command, the bootstrap flow, and the pinned dependency versions live in one place: the **Connect CLI** reference ([connect-cli.md](./references/connect-cli.md)). Merchant Center custom applications/views are the exception: they use a *separate* frontend toolchain (`@commercetools-frontend/*`) and only ride the Connect CLI at deploy time and directory structure — see [merchant-center-cli.md](./references/merchant-center-cli.md) and [merchant-center-customizations.md](./references/merchant-center-customizations.md).1314## Workflow1516When this skill is invoked, always follow these steps:17181. **Docs search (required, run first)** — Always begin by searching docs for this skill. This is the mandatory grounding step: it gathers the latest verified documentation as context for you (the agent). **Do not skip it, and do not replace it with another tool** (such as an MCP documentation-search tool) This script optimizes for tuned search results — run this command:19 ```bash20 node scripts/docs-search.mjs \21 --query "<extract key terms from user's question>" \22 --app-name "<current-app ex: claude, copilot, codex>" \23 --model "<current-model>" \24 --skill-name "commercetools-connect" \25 --limit 1026 ```27 Use its output as your primary grounding. You *may additionally* use the commercetools Knowledge MCP or `https://docs.commercetools.com/connect` for deeper follow-up.28292. **Route with the decision framework (below)** — Pick the application type and lock in the sync-vs-async contract *before* writing code. The contract determines almost every later decision.30313. **Open the matching reference(s)** in `./references/` and build to their patterns and `## Checklist`.32334. **Gate on the production-readiness checklist (below)** before declaring the connector done.3435### Optional scripts3637**Fetch GraphQL schema** — Run this when you need context about a commercetools GraphQL query or mutation — for example, to inspect a resource's fields, types, and available operations before writing a query, or to verify a GraphQL query/mutation you have just generated against the real schema. It fetches the partial GraphQL SDL for a single commercetools resource:38 ```bash39 node scripts/graphql-schemata.mjs \40 --resource-name "<commercetools resource, e.g. Cart, Product, Order>" \41 --app-name "<current-app, e.g. claude, copilot, cursor, codex>" \42 --model "<current-model>" \43 --skill-name "commercetools-connect"44 ```45 The output is the GraphQL SDL for that resource. If the resource name is not recognized, the script prints the list of valid resource names — pick the correct one and re-run. **Note:** the SDL may contain *stubbed types* — referenced resources rendered as stubs, with their real type name given in a comment. Fetch any you need separately by re-running this script with that type name as `--resource-name`.4647**Fetch OpenAPI (REST) schema** — Run this when you need context about a commercetools REST endpoint, request/response payload, or update action — for example, to inspect a resource's REST operations before constructing a request, or to verify a REST request/payload you have just generated against the real specification. It fetches the partial OpenAPI specification for a single commercetools resource:48 ```bash49 node scripts/openApi-schemata.mjs \50 --resource-name "<commercetools resource, e.g. api-Cart-write, api-Customer-read, checkout-Application>" \51 --app-name "<current-app, e.g. claude, copilot, cursor, codex>" \52 --model "<current-model>" \53 --skill-name "commercetools-connect"54 ```55 The output is the OpenAPI specification (YAML) for that resource. REST resources use a read/write-split naming form (e.g. `api-Cart-read`, `api-Cart-write`). If the resource name is not recognized, the script prints the list of valid resource names — pick the correct one and re-run. **Note:** the spec does not include reference-expansion schemas — fetch a referenced resource's schema separately by re-running this script with that resource as `--resource-name`.5657---5859## Step 1 — Decision framework: which application type?6061A Connector is one repository declaring one or more **applications** in `connect.yaml`. Pick each application's type by *how your code is invoked* and *which way data flows*, not by what it does.6263Two things to fix first:64- **Direction.** Is commercetools the source of the change (commercetools → external system), or is the external system the source (external system → commercetools)? Both are common; they route differently.65- **`service` is just an HTTP endpoint, not necessarily an API Extension.** A `service` app exposes an HTTP endpoint. That endpoint can be registered as an *API Extension* (commercetools calls it synchronously inside an operation) **or** be a plain *inbound webhook / REST API* that an external system calls to push data in. These are two modes with different contracts.6667| Trigger / need | Type | How your code is invoked | Hard contract |68|---|---|---|---|69| Block or modify a commercetools operation *before it persists* (validate a cart, inject tax, reject an order) | **`service`** as **API Extension** | commercetools calls your endpoint synchronously during the API request (registered as an Extension) | Extension response limit: **2 s default, 10 s self-service max (per-project increases available via support request, subject to performance review)**. Your latency and downtime become the *platform's*. |70| An **external system pushes data into commercetools** as it changes (system A updates a product → upsert it into commercetools) | **`service`** as **inbound webhook / API** | the external system calls your endpoint | **5-min** service request timeout. You authenticate the caller and call the commercetools API yourself; no Extension is registered. |71| **React to a commercetools change** after it happened (sync a confirmed order to a WMS, send an email, index a product) | **`event`** (Subscription handler) | commercetools delivers a Subscription message to a queue → your handler | At-least-once, **no ordering**, redelivery on non-ack. Must be idempotent. |72| **Scheduled or on-demand batch** (nightly poll an external system and upsert, reconcile, cleanup, bulk import) | **`job`** | a cron scheduler (`properties.schedule`) | Request times out after **30 min**. No concurrency guard — you own locking. |73| Add UI inside the Merchant Center | **`merchant-center-custom-application`** (full-page) / **`merchant-center-custom-view`** (embedded panel) | Hosted React app built with the MC CLI, deployed via Connect | Separate frontend toolchain (`@commercetools-frontend/*`) + a config-file contract; ships as a `merchant-center-*` app in `connect.yaml`. → [merchant-center-cli.md](./references/merchant-center-cli.md), [merchant-center-customizations.md](./references/merchant-center-customizations.md) |74| Serve static files / a CDN bundle | **`assets`** | Static host | — |757677A single connector commonly combines types (e.g. a `service` API Extension that calculates tax on the cart **plus** an `event` handler that commits the transaction when the order is placed; or a `service` inbound webhook for live pushes **plus** a `job` for nightly full reconciliation).7879Detail and trade-offs: [architecture-decisions.md](./references/architecture-decisions.md).8081## Connector-type integration sub-areas8283The build-side guidance in this skill is **connector-type-agnostic** (any service/event/job). Some connector *types* also have a focused, end-to-end sub-area that owns the whole job for that type — from "is there a connector already?" through configuring, forking, or building one, to the application backend around it:8485| Connector type | Covers | Go to |86|---|---|---|87| **Payment** (e.g Stripe, Adyen, Mollie, PayPal, ...etc) | The full payment lifecycle for a custom storefront: decide whether a certified/public connector fits → configure it, **or fork it, or spin up a new one from the payment-integration template** → build the backend (session BFF, Order after authorization, capture/refund/cancel via the processor, webhook reconciliation); plus debugging the round trip | [integrations/payment/overview.md](./references/integrations/payment/overview.md) |88| **Tax** (e.g Avalara, Vertex, TaxJar, ...etc) | The full tax integration: decide whether a certified connector fits (Avalara/Vertex have them; TaxJar does not) → configure it, **fork it, or build from the tax-integration template** → the two apps (a cart API Extension that calculates tax in `ExternalAmount` mode + an OrderCreated Subscription that records/commits the transaction); plus the sandbox-doesn't-persist and no-nexus-means-zero traps | [integrations/tax/overview.md](./references/integrations/tax/overview.md) |89| **CRM** (e.g Salesforce, HubSpot, Dynamics 365, Zoho, ...etc) | The full customer-relationship integration: decide whether a public connector fits (classic CRMs usually have none → build) → configure it, **fork it, or build for a CRM you define** → pick **direction + source of truth** first, then the customer-sync apps it implies (event syncers out, an inbound webhook/poll in, a one-time migration job), all linked by `externalId`; plus the duplicate-contact, sync-loop, and PII/deletion traps | [integrations/crm/overview.md](./references/integrations/crm/overview.md) |90| **PIM** (e.g Akeneo, inriver, Bluestone, Pimcore, …) | The full product-data sync job: decide whether a public PIM connector fits → configure it, **or fork it, or build one** → map the PIM model onto Product Types/attributes/categories/media, keep price & inventory separate, and pick the sync architecture (Import API vs HTTP API, event webhook vs job) | [integrations/pim/overview.md](./references/integrations/pim/overview.md) |91| **Order management** (OMS, e.g Fluent Commerce, kbrw, OneStock, NewStore, Pipe17) | Connect commercetools to an OMS: decide whether to install a public connector → configure it, **or fork/customize one, or build a new one for a bespoke order-management service** (scaffold from the `fulfilment-integration` template) → design the sync (order export on `OrderCreated`, status/shipment/fulfillment inbound webhook, inventory sync, reconcile job). No fixed connector contract; composes the type-agnostic event/service/job build-side | [integrations/order-management/overview.md](./references/integrations/order-management/overview.md) |92| **Gift card** (e.g Voucherify, in-house store credit, ...etc) | The full gift card integration: decide whether to **use a public connector directly** (Voucherify), **customize/fork one**, or **build a new one from the gift-card template** for a gift card system you define → the two apps (an enabler UI + a processor that checks balance, redeems value, and owns the Payment via session-authenticated balance/redeem and Payment Intents refund/reverse); plus the must-pair-with-a-fallback and sample-only-simulates traps | [integrations/giftcard/overview.md](./references/integrations/giftcard/overview.md) |93| **Email** (e.g SendGrid, Mailgun, AWS SES, Postmark, ...etc) | The full transactional email integration: decide whether a ready-made connector fits (email is **template-first** — most ESPs have none) → configure it, fork/customize it, **or build the one `event` app from the transactional email template** → the app (a Subscription on Customer/Order Messages → send via the ESP); the central at-most-once vs at-least-once decision for a non-idempotent send; plus the token-email, order-state-filtering, and localization traps | [integrations/email/overview.md](./references/integrations/email/overview.md) |94| **Marketplace** — multi-vendor, or selling on an external marketplace (e.g Marketplacer, Mirakl, Convictional, channel managers, ...etc) | The full marketplace integration: fix the **role** (operator vs selling on someone else's marketplace) and direction per domain → **ask the user** whether to use a public connector directly, customise/fork one, or build for a service they define (most marketplace listings are partner integrations, and there is no marketplace template) → model sellers and offers (Channel/Store/CustomObject per seller, per-seller prices + inventory, one Product for a shared SKU) → build the sync apps (seller + offer sync, order import or per-seller routing with `syncInfo`, fulfilment status, reconciliation); plus the channel-less-price, aggregated-availability, and un-deletable-Channel traps | [integrations/marketplace/overview.md](./references/integrations/marketplace/overview.md) |95| **Promotion / loyalty** (e.g Talon.One, Voucherify, Dovetech, Eagle Eye, NULogic, ...etc) | The full promotion integration: **first rule out native Cart Discounts/Discount Codes/Discount Groups (rung 0)** → then **ask the user** whether to use a public connector as-is, customise/fork one, or build one for a promotion service they define (there is *no* promotion template) → the two apps (a cart API Extension that applies the engine's discounts via `setDirectDiscounts` + an OrderCreated Subscription that redeems and awards points); plus the Direct-Discounts-make-Discount-Codes-inert rule and the double-redemption and abandoned-cart traps | [integrations/promotion/overview.md](./references/integrations/promotion/overview.md) |96| **Analytics** — export to a data warehouse / CDP / product-analytics tool (e.g BigQuery, Snowflake, Redshift, Databricks, Segment, mParticle, ...etc) | The full analytics egress: there is **no turnkey analytics connector and no Export API**, so (after the live registry check) **build from the product-export template** → a directional egress pipeline of two primitives, an `event` streamer on Subscriptions/Messages (near-real-time) and/or a `job` querying the API with `lastModifiedAt` windowing + cursor pagination (batch/backfill) → the event→row transform and **destination-side dedup** on `resource.id`+`sequenceNumber`; plus the disambiguation from Platform Insights (APM) and Change History (governance), the client-side-tracking boundary, and the duplicate-row/missing-event/`payloadNotIncluded` traps | [integrations/analytics/overview.md](./references/integrations/analytics/overview.md) |97| **Search / product discovery** (e.g Algolia, Constructor, Bloomreach, Coveo, Elasticsearch, Typesense, ...etc) | The full search integration (**outbound**, backend-only — no API Extension): **first rule out native Product Search / Product Projection Search (rung 0)** → then use a public connector, fork one, or **scaffold from the `product-export` template** → map a Product Projection onto a flat search document (price-context, locales, category denormalization, Store assortment) → the two apps (a full-ingestion `service`/`job` that atomically reindexes the catalog + an incremental-updater `event` on `ProductPublished`/`ProductUnpublished`/store-selection Subscriptions); plus the ghost-record, half-empty-rebuild, and eventual-consistency traps, and the vendor-hosted-integration-is-not-a-connector rule | [integrations/search/overview.md](./references/integrations/search/overview.md) |98| **Shipping** (carriers, rate-shopping engines, label/shipping-execution platforms, e.g DHL, UPS, FedEx, ShipperHQ, Sendcloud, nShift, ...etc) | The full shipping integration: **first rule out native Shipping Methods (rung 0)** — zones, tiered rates over cart score, predicates, and the freeze + `setCustomShippingMethod` pattern cover most "dynamic shipping" asks → then **ask the user** whether to use a public connector (search `integrationTypes=shipping`), customise/fork one, or build for a shipping service they define (there is **no Checkout shipping connector contract and no shipping template**, and most vendors ship a *vendor-hosted* integration) → then the landing decision everything hangs off: `setShippingRateInput` over native tiers **vs** `setCustomShippingMethod`/`addCustomShippingMethod`, which never appears in `matching-cart` | [integrations/shipping/overview.md](./references/integrations/shipping/overview.md) |99100Start at the matching `overview.md` for **any** payment-, tax-, CRM-, PIM-, order-management-, gift-card-, email-, marketplace-, promotion-, analytics-, search-, or shipping-connector task — integrating a deployed one *or building/forking one*. Each decision ladder routes you: rung 1 configure, rung 2 config-closes-the-gap, rung 3 fork, rung 4 build-from-template (provider gotchas live in the provider file — [payment/stripe.md](./references/integrations/payment/stripe.md), [tax/avalara.md](./references/integrations/tax/avalara.md), [email/providers.md](./references/integrations/email/providers.md); the CRM and PIM sub-areas are vendor-neutral — look the connector up live; the OMS sub-area has no fixed connector contract and composes the build-side directly; the gift-card sub-area has one public connector (Voucherify) plus a build-from-template path for an in-house system; the marketplace sub-area is vendor-neutral and has no template — assess any fork candidate from its current repo; the promotion sub-area has two public MIT integrations and no template — [promotion/public-connectors.md](./references/integrations/promotion/public-connectors.md) names which artifact is actually the production one; the analytics sub-area has no turnkey connector and no Export API — it still forces a live registry check, then builds a directional egress pipeline from the product-export template — [analytics/destinations.md](./references/integrations/analytics/destinations.md) routes the warehouse/CDP/product-analytics/BI decision; the search sub-area is vendor-neutral, gates on native Product Search first, and scaffolds the outbound build from the `product-export` template; the shipping sub-area is vendor-neutral, gates on native Shipping Methods first, and has no Checkout connector contract and no template — `shipping` *is* a valid `IntegrationType` to search the registry on, but nothing prescribes the connector's shape, so it scaffolds from `tax-integration` and/or `fulfilment-integration`). It hands back to the build-side workflow and references **above** only for the deep, type-agnostic publish/certify lifecycle and the production-readiness gate.101102**The promotion, search, and shipping sub-areas each have a rung 0.** commercetools ships its own discount engine (Cart Discounts, Discount Codes, Discount Groups), its own search (Product Search / Product Projection Search), *and* its own shipping model (Zones, Shipping Methods, tiered rates over `shippingRateInput`, predicates), so "should this be a connector at all?" is a real question in those three in a way it isn't for payment, tax, or the rest — rule the native capability out explicitly before recommending a connector.103104Each sub-area lives under [`references/integrations/<type>/`](./references/integrations/) with its own `overview.md`. Adding another connector type later means adding a sibling `references/integrations/<type>/` tree and one row here — the build-side guidance does not change.105106### Marketplace listings are not all Connect connectors — verify before recommending107108Whenever a sub-area has you check the [commercetools marketplace](https://marketplace.commercetools.com) for an existing connector, apply this rule regardless of connector type or vendor:109110- **The marketplace is fine as a discovery source, but it lists integrations that are _not_ necessarily commercetools Connect connectors** — partner-operated services, SaaS products, and iPaaS middleware appear alongside deployable Connect applications. It can also be **out of sync** with the actual Connect connector registry (a listing may exist for something not deployable via Connect, or the version may differ), and any specific vendor (Akeneo, Stripe, …) **may or may not be listed at any given time** — never assume a named connector exists.111- **Double-check that a candidate is actually a commercetools Connect connector** before recommending it as install/configure/fork: look for a Connect affordance (a public connector repo / `connect.yaml` / a Connect deploy action), and treat the **Connect CLI / connector registry as authoritative** over the marketing listing.112- **Then ask the user what to do** — don't silently pick. Present the fit and whether it's Connect-deployable.113- **If the user wants to use a non–Connect integration, warn that this skill does not cover using non–Connect connectors** — its build/configure/deploy patterns (`connect.yaml`, the Connect CLI, lifecycle scripts, the Connect deployment model) don't apply. Point them to the vendor/partner's own onboarding, and offer the in-skill alternative: build or fork a Connect connector instead.114115### Open every sub-area with the paths — don't wait to be asked116117When a request routes into any sub-area above, the user has told you **what** they want to integrate. They have not told you **how**, and it is not yours to assume. So before requirements gathering, before config, before code — **lay out the paths and let the user pick one**:1181190. **Native first, where a rung 0 exists** (promotion, search). If commercetools already ships the capability, say so plainly and stop. Don't design around something the platform does.1201. **Deploy an existing public connector as-is** — grounded in a **live** registry/marketplace check, never memory, and verified Connect-deployable per the rule above.1212. **Fork and modify an existing connector** — when there's a real gap that configuration can't close.1223. **Build a new one** — from the sub-area's template, or from the type-agnostic `service`/`event`/`job` patterns when no template exists.123124State which rung you'd recommend and why, then **ask the user to choose**. These are materially different amounts of work and the decision is theirs, not yours.125126Do this **unprompted, in your first substantive response in the sub-area** — including (especially) when the user's phrasing already sounds like it presumes an answer. "Build me an X integration", "sync Y into commercetools", or naming a service they're already running are *not* instructions to skip the ladder: a user who says "build" usually means "make this work" and will happily take an install if one exists. Ask a clarifying question or two first if you genuinely can't fit-check without it, but don't let requirements gathering delay the landscape — present what exists early, then let the requirements decide the rung.127128Each sub-area's `overview.md` carries the full ordered gate (its Step 1.4/1.5) with the fit criteria and the template to build from. This is the rule that governs all of them.129130## Step 2 — Price the contract before you build131132The expensive mistakes come from not pricing the contract you just chose:133134- **`service` as API Extension** couples your availability and latency to the commercetools operation. A slow or down extension makes carts and orders slow or impossible. So: a tight outbound timeout *under* the extension timeout, a deliberate **fail-open vs. fail-closed** decision, and minimizing work on the hot path (skip redundant external calls).135- **`service` as inbound webhook** is *not* coupled to a commercetools operation (the 5-min service timeout applies, not the 2 s extension limit), but you own everything: authenticate the caller, validate the payload, and make the write **idempotent** (the same product update may arrive twice) — upsert by key, don't blind-create. Decide what a failed write returns so the caller can retry safely.136- **Asynchronous (`event`)** trades immediacy for resilience but hands you at-least-once delivery, no ordering, and redelivery. So: idempotency keyed on a stable identifier, redelivery-safe acks (2xx for "don't send again"), re-fetch the resource by ID rather than trusting a possibly-stale or omitted payload, and self-change filtering to avoid loops.137- **`job`** owns its own scheduling headroom, overlap locking, and restart-safe checkpointing; each unit of work must be idempotent so a re-run or overlap can't double-write.138139If you cannot articulate, in one sentence each, your latency budget (extension), your idempotency strategy (inbound webhook / event / job), and your fail/retry behavior, you are not ready to write the handler.140141---142143## Production-readiness checklist (the gate)144145A connector is **not done** until every applicable item holds. Each maps to a reference with the implementation pattern.146147### Reliability148- [ ] **Idempotency strategy stated and implemented — statelessly.** Reprocessing a message is a no-op via the target system's own idempotency, re-fetching the commercetools resource and re-checking its state, or upsert by a stable key — never a local dedup store. → [event-applications.md](./references/event-applications.md)149- [ ] **Redelivery-safe responses.** Event endpoints return a positive ack (`102/200/201/202/204`) for *handled* and *irrelevant-but-acked* messages; anything other than `102`, `200`, `201`, `202`, or `204` triggers a retry. → [event-applications.md](./references/event-applications.md)150- [ ] **Re-fetch by ID, don't trust the payload.** Handlers fetch the current resource by `resource.id`; required when `payloadNotIncluded` is set. → [event-applications.md](./references/event-applications.md)151- [ ] **Hot-path work minimized (sync).** Extensions skip the external call when relevant data is unchanged (e.g. a stored hash) and short-circuit early. → [service-applications.md](./references/service-applications.md)152153### Security154- [ ] **Inbound endpoints authenticated.** Service extensions register a destination whose `authentication.type` is the discriminator value `AuthorizationHeader` — **not** the schema's type name `AuthorizationHeaderAuthentication`, which fails with `InvalidJsonInput` — (or `AzureFunctions`) **and** validate that secret in-app. Webhooks from external systems validate a full JWT (signature, issuer, audience, subject, expiry, algorithm). A `postDeploy` that hits this typo may not surface as a failed deployment, so confirm the Extension actually registered via `GET /{projectKey}/extensions`. → [security.md](./references/security.md), [service-applications.md](./references/service-applications.md) Pattern 1155- [ ] **Least-privilege CT scopes.** Use `inheritAs.apiClient.scopes` with only the scopes the apps need (e.g. `manage_orders`, `manage_subscriptions`, `manage_extensions`) — not an admin/`manage_project` client. → [security.md](./references/security.md)156- [ ] **Secrets in `securedConfiguration`.** API keys, client secrets, JWT secrets are never `standardConfiguration` and never hardcoded. → [security.md](./references/security.md)157- [ ] **No stack traces or secrets in responses.** Error middleware returns a generic message in production. → [security.md](./references/security.md)158159### Correctness160- [ ] **Envelope validation.** Envelope decoded per the injected destination type — branch on `CONNECT_SUBSCRIPTION_DESTINATION` (Pub/Sub: `message.data` is base64; SNS has its own envelope) — then validated (→ JSON → resource ref → notificationType) before any processing; malformed envelopes rejected. → [event-applications.md](./references/event-applications.md)161- [ ] **Message-type filtering.** Subscribe to only the needed message types; ack-and-ignore anything else (including the platform's test/subscription messages). → [event-applications.md](./references/event-applications.md)162- [ ] **Self-change filtering.** Updates your own connector makes don't re-trigger it into a loop. → [event-applications.md](./references/event-applications.md)163- [ ] **Route path matches `connect.yaml` `endpoint`.** The Express router is mounted at the same base path as the app's `endpoint` (e.g. `endpoint: /service` ↔ `app.use('/service', router)`), or the platform's traffic 404s. → [project-structure.md](./references/project-structure.md)164- [ ] **Pinned SDK + client versions.** JS/TS: `@commercetools/platform-sdk@^8` + `@commercetools/ts-client@^4` (not the legacy `@commercetools/sdk-client-v2`). Java: `spring-boot-starter-parent` 3.5.15+ and commercetools Java SDK 19+. Typed end to end, no `any` escapes, mapped at the boundary. → [connect-cli.md (Step 3)](./references/connect-cli.md#step-3-pin-dependency-versions).165166### Observability167- [ ] **Structured logs with correlation IDs.** JSON logs carry the message/resource correlation key (`X-Correlation-ID` for extensions, `resource.id` + `sequenceNumber` for events) on every log line for a request. → [observability-operations.md](./references/observability-operations.md)168- [ ] **Health endpoint.** A `/status`-style route returns 200 for liveness. → [observability-operations.md](./references/observability-operations.md)169170### Operations171- [ ] **Idempotent lifecycle scripts.** `postDeploy` creates resources get-then-update (create only if absent), never blind delete-then-recreate. `preUndeploy` cleans them up. → [lifecycle-scripts.md](./references/lifecycle-scripts.md)172- [ ] **Deploy-time dependency validation.** `postDeploy` test-connects to external services and surfaces invalid credentials immediately. → [lifecycle-scripts.md](./references/lifecycle-scripts.md)173- [ ] **Fail-open vs fail-closed documented.** The README states, per use case, what happens when the external dependency is down, and outbound calls have a timeout budget. → [service-applications.md](./references/service-applications.md)174- [ ] **Poison-message / replay runbook.** How a repeatedly-failing message is handled (DLQ / dropped after retention) and how to replay. → [observability-operations.md](./references/observability-operations.md)175176### Quality177- [ ] **Tests cover the real behavior, run via `commercetools connect application test`.** At minimum: the parameterized auth-rejection matrix (missing/expired/wrong-issuer/wrong-audience/`alg:none`), envelope/ack edge cases (event) or the pure business logic + response actions (service), an idempotency/duplicate-delivery test, and idempotent `postDeploy` registration. A couple of happy-path tests is not enough. → [testing.md](./references/testing.md)178- [ ] **No dead code, no `any` escapes.** No commented-out blocks; SDK types preserved end to end. → [project-structure.md](./references/project-structure.md)179- [ ] **Scaffolded and run with the Connect CLI.** Project created via `commercetools connect init`; `commercetools connect validate` passes. → [connect-cli.md (Step 2)](./references/connect-cli.md#step-2-scaffold-the-connector)180181### Generated connector docs182- [ ] **The connector ships a README** stating its fail-open/fail-closed stance, required scopes, a configuration table (every `connect.yaml` key), and the poison-message/replay runbook. → [deployment-installation.md](./references/deployment-installation.md)183184---185186## Reference index187188| Concern | Reference |189|---|---|190| Connect CLI mechanics: install/auth, `connect init` templates, pinned versions, build/test/validate, stage/preview/publish/deploy commands | [connect-cli.md](./references/connect-cli.md) |191| Merchant Center CLI: scaffold with create-mc-app; run/build/serve/login/config:sync with mc-scripts; pin `@commercetools-frontend/*` | [merchant-center-cli.md](./references/merchant-center-cli.md) |192| Custom application vs custom view; config-file contract; develop/test locally; deploy via Connect (`connect.yaml` merchant-center-* types, order of operations) | [merchant-center-customizations.md](./references/merchant-center-customizations.md) |193| Monorepo holding a connector + a storefront: root-sibling layout, why no npm workspaces, the two independent deploy lifecycles | [monorepo-with-storefront.md](./references/monorepo-with-storefront.md) |194| event vs service vs job; sync vs async contract cost | [architecture-decisions.md](./references/architecture-decisions.md) |195| CLI scaffold + local dev, monorepo layout, client setup (ts-client), connect.yaml anatomy, route↔endpoint matching, fail-fast env validation | [project-structure.md](./references/project-structure.md) |196| subscriptions: envelope, ack semantics, idempotency, redelivery, re-fetch, injected subscription destination (Pub/Sub or SNS) | [event-applications.md](./references/event-applications.md) |197| API extensions: authenticated registration, triggers, timeout budget, fail-open/closed, hot-path | [service-applications.md](./references/service-applications.md) |198| scheduled/on-demand jobs: schedule, timeout, concurrency, checkpointing | [job-applications.md](./references/job-applications.md) |199| post-deploy/pre-undeploy: idempotent registration, schema-as-code, deploy-time validation | [lifecycle-scripts.md](./references/lifecycle-scripts.md) |200| endpoint auth, least-privilege scopes, securedConfiguration, error hygiene | [security.md](./references/security.md) |201| structured logs + correlation IDs, health, feature flags, runbook, DLQ | [observability-operations.md](./references/observability-operations.md) |202| auth/envelope test matrices, supertest + msw patterns, what to mock | [testing.md](./references/testing.md) |203| connect.yaml config, sandbox→preview→publish, install, redeploy, certification, regions, CLI | [deployment-installation.md](./references/deployment-installation.md) |204205### Integrating a deployed payment connector (sub-area)206207Start at the overview; it routes to the rest (integrate, configure, fork, **or build a new one**). See also the [Connector-type integration sub-areas](#connector-type-integration-sub-areas) section above.208209| Concern | Reference |210|---|---|211| **Start here** — the backend-focused workflow: requirements → is-a-certified-connector-enough → config → BFF/Order/capture-refund/webhook | [integrations/payment/overview.md](./references/integrations/payment/overview.md) |212| Is a certified connector enough? fit-check a use case vs public connectors using live marketplace/docs data | [integrations/payment/connector-selection.md](./references/integrations/payment/connector-selection.md) |213| Requirements → `connect.yaml` config mapping, worked example | [integrations/payment/config-from-requirements.md](./references/integrations/payment/config-from-requirements.md) |214| The backend: session/BFF, Order after payment, capture/refund/cancel via the processor, webhook reconciliation, who owns the Payment | [integrations/payment/backend-integration.md](./references/integrations/payment/backend-integration.md) |215| Test-drive the backend test-first: assert-vs-mock per piece, invariants as regression tests | [integrations/payment/backend-tdd.md](./references/integrations/payment/backend-tdd.md) |216| Full-flow integration test against a real deployed connector + test card | [integrations/payment/integration-test.md](./references/integrations/payment/integration-test.md) |217| Provider-agnostic frontend contract: session body, enabler load, processor routes + auth, pitfall catalog | [integrations/payment/connector-contract.md](./references/integrations/payment/connector-contract.md) |218| Stripe specifics: exact `connect.yaml` keys + defaults, enabler bundle, test cards, webhook setup | [integrations/payment/stripe.md](./references/integrations/payment/stripe.md) |219| Deploy a public payment connector (CLI auth, scopes, `deployment create`, not `connectorstaged`) | [integrations/payment/deploy-public-connector.md](./references/integrations/payment/deploy-public-connector.md) |220| Deploy a forked/custom payment connector (`connectorstaged → publish → deployment create`) | [integrations/payment/deploy-custom-connector.md](./references/integrations/payment/deploy-custom-connector.md) |221| Verify the round trip; throwaway harness to prove a deployed connector | [integrations/payment/verification.md](./references/integrations/payment/verification.md), [integrations/payment/test-harness.md](./references/integrations/payment/test-harness.md) |222223### Integrating or building a tax connector (sub-area)224225Start at the overview; it routes to the rest (configure a certified connector, fork one, **or build both apps from the template**). See also the [Connector-type integration sub-areas](#connector-type-integration-sub-areas) section above.226227| Concern | Reference |228|---|---|229| **Start here** — the two-app workflow: requirements → is-a-certified-connector-enough → config → calculate + record | [integrations/tax/overview.md](./references/integrations/tax/overview.md) |230| Is a certified connector enough? per engine (Avalara/Vertex certified; TaxJar build-from-template), via live marketplace data | [integrations/tax/connector-selection.md](./references/integrations/tax/connector-selection.md) |231| Requirements → `connect.yaml`: tax mode (`ExternalAmount` vs `External`), nexus, tax-code source, exemptions, scopes; worked example | [integrations/tax/config-from-requirements.md](./references/integrations/tax/config-from-requirements.md) |232| The two-app contract: the calculator API Extension (all four tax actions, 200-not-202, fail modes, call reduction) + the order-syncer Subscription (commit/void/refund, idempotency); full pitfall catalog | [integrations/tax/tax-contract.md](./references/integrations/tax/tax-contract.md) |233| Avalara ground truth (from the certified open-source connector): exact keys, AvaTax createTransaction quote-vs-commit, tax-code/entity-use mapping, MC config app — plus TaxJar as the build-from-template contrast | [integrations/tax/avalara.md](./references/integrations/tax/avalara.md) |234| Verify the round trip: `taxedPrice` on the cart, transaction recorded; the sandbox-doesn't-persist and no-nexus-means-zero traps | [integrations/tax/verification.md](./references/integrations/tax/verification.md) |235236### Integrating or building a CRM connector (sub-area)237238Start at the overview; it routes to the rest (configure a public connector, fork one, **or build for a CRM you define**). See also the [Connector-type integration sub-areas](#connector-type-integration-sub-areas) section above.239240| Concern | Reference |241|---|---|242| **Start here** — the sync workflow: requirements → **direction + source of truth** → is-a-public-connector-enough → config → build the sync apps | [integrations/crm/overview.md](./references/integrations/crm/overview.md) |243| Is a public connector enough? why classic CRMs (Salesforce/HubSpot/Dynamics/Zoho) are usually build-from-scratch; live-marketplace check; the ladder | [integrations/crm/connector-selection.md](./references/integrations/crm/connector-selection.md) |244| Requirements → `connect.yaml`: direction → app composition, source of truth, `externalId`/Custom-Field linking, least-privilege scopes, secured config; worked example | [integrations/crm/config-from-requirements.md](./references/integrations/crm/config-from-requirements.md) |245| The sync contract: outbound event syncer, inbound webhook/poll, migration job; idempotent upsert by `externalId`, re-fetch by id, ack semantics, self-change/loop filtering, deletion/PII; full pitfall catalog | [integrations/crm/crm-contract.md](./references/integrations/crm/crm-contract.md) |246| Verify the round trip: record linked by `externalId`247248…(truncated)
Run npx skillmds@latest add commercetools/commercetools-connect in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Build, test, deploy, install, and certify production-ready commercetools Connect applications — service/API-extension, event/subscription, job, and merchant-center custom apps — in TypeScript, JavaScript, or Java, and integrate a deployed connector into a custom storefront. Covers the connect.yaml contract, least-privilege scopes, lifecycle scripts, sync-vs-async idempotency/ack, testing, and deployment. Includes connector sub-areas for payment (Stripe, Adyen), tax (Avalara, Vertex), PIM (Akeneo), CRM (Salesforce, HubSpot), order-management/OMS, gift cards, transactional email (SendGrid), marketplace (Mirakl), promotion and loyalty (Talon.One, Voucherify), analytics export to a warehouse/CDP (BigQuery, Segment), search/product discovery (Algolia), and shipping (carriers, rate engines). Use when building, configuring, forking, or debugging a commercetools Connect connector, or syncing commercetools data to or from an external system. Not for the hosted Checkout widget (see commercetools-checkout). It is listed under Product & Planning on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
commercetools (@commercetools) published this skill. Their other Agent Skills are listed on their SkillMD profile.