Wiring and Packaging
With all the pieces built, it’s time to wire them into an installable connector and set up the package structure.
ConnectorDef
Section titled “ConnectorDef”Ties schema, resolvers, operations, seeder, and onboarding into a single descriptor:
import { ConnectorDef, ConnectorModule, Installation } from "@max/connector";import { Context } from "@max/core";import { AcmeOperations } from "./operations.js";
const AcmeDef = ConnectorDef.create<AcmeConfig>({ name: "acme", displayName: "Acme", description: "Project management connector powered by Acme", icon: "", version: "0.1.0", scopes: [], schema: AcmeSchema, onboarding: AcmeOnboarding, seeder: AcmeSeeder, resolvers: [ AcmeRootResolver, AcmeUserResolver, AcmeWorkspaceResolver, AcmeProjectResolver, ], operations: [...AcmeOperations],});ConnectorModule
Section titled “ConnectorModule”Pairs the def with an initialise function that creates a live Installation:
const AcmeConnector = ConnectorModule.create<AcmeConfig>({ def: AcmeDef, initialise(config, credentials) { const tokenHandle = credentials.get(AcmeApiToken); const api = new AcmeConnection(config, tokenHandle);
const ctx = Context.build(AcmeAppContext, { api, workspaceId: config.workspaceId, });
return Installation.create({ context: ctx, async start() { await api.start(); credentials.startRefreshSchedulers(); }, async stop() { credentials.stopRefreshSchedulers(); }, async health() { const result = await api.health(); return result.ok ? { status: "healthy" } : { status: "unhealthy", reason: result.error ?? "Unknown error" }; }, }); },});
export default AcmeConnector;The default export is the ConnectorModule. This is what the registry imports.
What happens during initialise
Section titled “What happens during initialise”credentials.get(AcmeApiToken)returns aCredentialHandle- a lazy handle, not the raw secret- You build your API client wrapper, passing the handle (credentials aren’t resolved yet)
Context.build()hydrates the context class with real valuesInstallation.create()packages context + lifecycle hooks- Later, the platform calls
start()- that’s when credentials resolve and the HTTP client is constructed
Installation lifecycle
Section titled “Installation lifecycle”| Hook | When | Purpose |
|---|---|---|
start() | Before first sync | Resolve credentials, create HTTP clients |
stop() | On shutdown | Clean up schedulers, close connections |
health() | On demand | Lightweight connectivity check |
Package setup
Section titled “Package setup”Where connectors live
Section titled “Where connectors live”Connectors live in a connector collection - a directory (or repo) containing one or more connector-* folders. A collection can live anywhere; it doesn’t need to be inside the Max monorepo.
Install a collection into Max with:
# Local pathmax -g install --collection /path/to/my-connectors
# Git URLmax -g install --collection git@github.com:my-org/max-connectors.gitThe registry scans installed collections for connector-* folders, reads each package.json, and registers a lazy loader. Your connector is only imported when first needed.
Collection layout
Section titled “Collection layout”A collection is a Bun workspace with one or more connectors:
my-connectors/├── package.json├── connector-github/│ ├── package.json│ └── src/│ └── index.ts├── connector-linear/│ ├── package.json│ └── src/│ └── index.tsCollection root package.json
Section titled “Collection root package.json”{ "name": "my-connectors", "private": true, "workspaces": { "packages": ["connector-*"], "catalog": { "@max/core": "link:@max/core", "@max/connector": "link:@max/connector", "@types/bun": "latest", "typescript": "5.9.3" } }}The link: entries resolve to your local Max checkout via bun link. Running bun install in the Max monorepo links @max/core and @max/connector as global packages. Running bun install in your collection picks them up.
Connector package.json
Section titled “Connector package.json”{ "name": "@max/connector-acme", "version": "0.1.0", "type": "module", "exports": { "types": "./src/index.ts", "default": "./src/index.ts" }, "scripts": { "build": "bun build ./src/index.ts --outdir ./dist --target node", "typecheck": "tsc --noEmit", "test": "bun test --pass-with-no-tests" }, "dependencies": { "@max/core": "catalog:", "@max/connector": "catalog:" }, "devDependencies": { "@types/bun": "catalog:", "typescript": "catalog:" }}Use "catalog:" for @max/core and @max/connector - this resolves through the collection root’s catalog. Point both types and default exports to source.
File structure
Section titled “File structure”Here’s the complete connector layout:
connectors/connector-acme/├── package.json├── tsconfig.json├── src/│ ├── index.ts # ConnectorDef + ConnectorModule (default export)│ ├── config.ts # TConfig interface│ ├── entities.ts # Entity definitions│ ├── schema.ts # Schema│ ├── credentials.ts # Credential declarations│ ├── context.ts # Context definition│ ├── onboarding.ts # OnboardingFlow│ ├── seeder.ts # Seeder + SyncPlan│ ├── acme-client.ts # API client wrapper│ └── resolvers/│ ├── root-resolver.ts│ ├── user-resolver.ts│ ├── workspace-resolver.ts│ └── project-resolver.tsYour connector is complete
Section titled “Your connector is complete”You now have a working, installable connector with:
- A typed data model (entities, schema, context)
- A sync pipeline (loaders, resolvers, seeder)
- Operations wrapping every API call
- An onboarding flow for user setup
- Assembly (ConnectorDef, ConnectorModule, Installation)
- A publishable package structure
For most connectors, this is everything you need. The next part covers an advanced optimization for connectors that need to extract multiple entity types from a single API endpoint.
Next: Advanced Patterns