Adding a New Connector

Step-by-step guide for adding a new commerce platform connector to Brainerce.

For the live reference implementation, read apps/google-youtube-connector/ in the monorepo. It is the connector currently in production and every contract below is exercised there. apps/google-merchant-connector/ is the superseded standalone service that google-youtube replaced; do not use either as a model.

Do not look for a TypeScript interface to implement: there is no ConnectorContract type anywhere in the monorepo, and packages/connectors/ holds legacy in-process mappers that only the Migration Tool's one-time import still uses. The endpoint list below is the contract, declared per app as connectorContract.endpoints in the manifest.

Quick Checklist

Endpoints: advertise only what you implement

The manifest lists the endpoints a connector serves, under integration.connectorContract.endpoints (endpoint name to path). A connector must advertise only endpoints it actually implements: the worker enqueues a job for every advertised endpoint, and a job that 404s at the connector exhausts its retries into the dead-letter queue. A pull-only platform simply leaves the push endpoints out. The names Brainerce knows, with the path each one conventionally uses:

Endpoint namePathPurpose
getConnectionStatusPOST /sync/connection-statusTest credentials, return { connected: boolean }
pushProductPOST /sync/push-productTransform and push one product, return { externalId }
pushInventoryPOST /sync/push-inventoryPush a stock level
pullProductsPOST /sync/pull-productsFetch products from the platform
pullOrdersPOST /sync/pull-ordersFetch and normalize orders, return { orders, hasMore }
reconcilePOST /sync/reconcileList all external resource IDs
pullCategories, pushCategory/sync/pull-categories, /sync/push-categoryCategories
pullTags, pushTag/sync/pull-tags, /sync/push-tagTags
pullBrands, pushBrand/sync/pull-brands, /sync/push-brandBrands
pullAttributes, pushAttribute/sync/pull-attributes, /sync/push-attributeAttributes
pushOrderNotes, pushOrderStatus, pushShipping, updateOrderFields/sync/push-order-notes, /sync/push-order-status, /sync/push-shipping, /sync/update-order-fieldsOrder write-back
getRefunds, createRefund/sync/get-refunds, /sync/create-refundRefunds
pushCoupon/sync/push-couponCoupons
deleteResource/sync/delete-resourceRemove a resource on the platform

Beyond this catalogue a manifest may add its own named endpoints (for example Google's discovery). Every connector also serves GET /health, returning { ok: true }.

Optional pieces, each declared in the manifest:

  • OAuth: connectorContract.oauth with startPath (POST /oauth/start, return the redirect URL) and callbackPath (POST /oauth/callback, exchange the code, return { secrets })
  • Lifecycle hooks: hooks.onInstall, hooks.onUninstall, hooks.onConfigUpdate (conventionally POST /hooks/on-install, /hooks/on-uninstall, /hooks/on-config-update; the last one validates a new config)
  • Live mirroring: connectorContract.eventSubscriptions (the event names the connector wants, such as product.created, order.created, inventory.changed) and connectorContract.incomingWebhooks (endpoint plus verificationMethod, for example hmac-sha256). A one-time import source leaves both out so it never registers live subscriptions

Manifest

  • App manifest with integration.type: "connector", integration.connectorContract.platform and integration.connectorContract.endpoints
  • Config schema (JSON Schema) defining the settings form
  • Secret fields use "format": "password"

Data Mapping

  • Product mapper: Brainerce format → platform format (handle overlays)
  • Order mapper: platform format → Brainerce normalized format
  • Handle edge cases: missing images, duplicate SKUs, variants

Canonical Product URL

Feed-style connectors (Google Merchant, Meta Catalog, …) need a link per product that points back to the merchant's storefront. The backend computes this from the linked Sales Channel's domain + productUrlTemplate and injects it into push-product calls as productUrl: string in the request body.

  • Read req.body.productUrl in push-product and use it as the platform's link / landing_url / equivalent field
  • Decide what to do when productUrl is absent, and document the choice. The backend deliberately leaves this to the connector: reject the push, or fall back to a URL shape of your own. The shipped reference falls back to `${websiteUrl}/products/${encodeURIComponent(slug)}` rather than rejecting. There is no platform-wide MISSING_PRODUCT_URL error code; if you reject, define your own.
  • The bare primaryDomain (hostname only) is also available on the StoreContext for connectors that need to derive other URLs (e.g. homepage verification). There is no primaryProductUrlTemplate on StoreContext; the resolved per-product productUrl in the request body is the supported input.

Dependencies

{
  "dependencies": {
    "@brainerce/app-sdk": "workspace:*",
    "express": "^4.21.0"
  }
}