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 name | Path | Purpose |
|---|---|---|
getConnectionStatus | POST /sync/connection-status | Test credentials, return { connected: boolean } |
pushProduct | POST /sync/push-product | Transform and push one product, return { externalId } |
pushInventory | POST /sync/push-inventory | Push a stock level |
pullProducts | POST /sync/pull-products | Fetch products from the platform |
pullOrders | POST /sync/pull-orders | Fetch and normalize orders, return { orders, hasMore } |
reconcile | POST /sync/reconcile | List all external resource IDs |
pullCategories, pushCategory | /sync/pull-categories, /sync/push-category | Categories |
pullTags, pushTag | /sync/pull-tags, /sync/push-tag | Tags |
pullBrands, pushBrand | /sync/pull-brands, /sync/push-brand | Brands |
pullAttributes, pushAttribute | /sync/pull-attributes, /sync/push-attribute | Attributes |
pushOrderNotes, pushOrderStatus, pushShipping, updateOrderFields | /sync/push-order-notes, /sync/push-order-status, /sync/push-shipping, /sync/update-order-fields | Order write-back |
getRefunds, createRefund | /sync/get-refunds, /sync/create-refund | Refunds |
pushCoupon | /sync/push-coupon | Coupons |
deleteResource | /sync/delete-resource | Remove 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.oauthwithstartPath(POST /oauth/start, return the redirect URL) andcallbackPath(POST /oauth/callback, exchange the code, return{ secrets }) - Lifecycle hooks:
hooks.onInstall,hooks.onUninstall,hooks.onConfigUpdate(conventionallyPOST /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 asproduct.created,order.created,inventory.changed) andconnectorContract.incomingWebhooks(endpointplusverificationMethod, for examplehmac-sha256). A one-time import source leaves both out so it never registers live subscriptions
Manifest
- App manifest with
integration.type: "connector",integration.connectorContract.platformandintegration.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.productUrlinpush-productand use it as the platform'slink/landing_url/ equivalent field - Decide what to do when
productUrlis 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-wideMISSING_PRODUCT_URLerror code; if you reject, define your own. - The bare
primaryDomain(hostname only) is also available on theStoreContextfor connectors that need to derive other URLs (e.g. homepage verification). There is noprimaryProductUrlTemplateonStoreContext; the resolved per-productproductUrlin the request body is the supported input.
Dependencies
{
"dependencies": {
"@brainerce/app-sdk": "workspace:*",
"express": "^4.21.0"
}
}