# Best Practices Patterns for running embedded connectors in production — starting with the question most integrators hit first: **what do you wire a freshly connected connector to by default?** ## You have the full Monad platform The API key your backend holds is a full Monad API key — the same key you'd use to drive Monad directly. Embedding only changes *where the connector form renders* (inside a Monad-hosted iframe, so secrets never touch your JavaScript); it does not restrict what your backend can build. That means the pipelines you stand up for your customers aren't limited to a simple input-to-output hop. Anything Monad offers is available to you: - **Transforms** — reshape, rename, filter, and normalize records before they land. - **Enrichments** — add context (geo-IP, threat intel, lookups) mid-pipeline. - **Multi-node routing** — fan out to several destinations, branch on [conditions](/routing/index), and chain transforms and enrichments in whatever order your product needs. - **Alerts and everything else in the platform** — the embedded connector is just one node in a pipeline you fully control. The embed SDK ships helpers for the common shapes (`buildDevNullPipeline` and the lifecycle helpers), but those are conveniences, not limits. When you want more, build the pipeline directly with `POST /v2/{org}/pipelines/` (see [Connector Lifecycle](/sdks/embed/lifecycle#the-shared-primitive)) and include as many transform, enrichment, and output nodes as you like. Your ability to use Monad on your customers' behalf is exactly the same as using Monad directly. ## Default ingress destinations When a user connects a **source** (ingress), you decide where its data lands. The user never sees any of this; they see "connected." Choose the default that matches your stage: ### Start with dev/null For demos, and for a "connected but not yet routed" state, wire the new input to a throwaway sink with the shipped helper — zero setup, and the connector immediately shows as Active: ```ts await buildDevNullPipeline({ request, org, inputId, inputName }); ``` This is the recommended default while you're getting an integration working. Nothing is shared, so it also has the simplest teardown (`CLEANUP_FULL`). ### Graduate to a real destination When you're ready to actually keep the data, build the pipeline to a real output with the raw `POST /v2/{org}/pipelines/` primitive (see [Connector Lifecycle](/sdks/embed/lifecycle#the-shared-primitive)). Common shapes: - **A per-tenant store** — each team's own destination (their S3, Snowflake, and so on). - **A global store** that every tenant feeds. - **A richer pipeline** — transforms or filters between the input and the output. Match the [cleanup policy](/sdks/embed/lifecycle#delete-and-cleanup) to the shape: `CLEANUP_FULL` for a throwaway sink, `CLEANUP_KEEP_OUTPUT` for a shared store other inputs still feed. ## Default egress source When a user connects a **destination** (egress), fix the input side: provision **one source per team, once**, and let every destination the tenant adds fan out from it. The source is your product's own connector or a simple HTTP input. Because that source feeds every tenant destination, never delete it. Egress teardown uses a custom cleanup that keeps the source (`input: false`) and removes only the pipeline and the user's output. ## Naming defaults Connector names are unique within a team, so two connectors of the same type would collide. Two levers: - **Omit `name`** and Monad appends a short unique suffix for you. - **Seed a unique name** to allow two of the same type, e.g. `` `${type.name} (${crypto.randomUUID().slice(0, 4)})` ``. Set `isNameEditable` / `isDescriptionEditable` if you want the user to name it inside the iframe (both default off). See the [options reference](/sdks/embed/reference#createconnectorframe-options). ## Keep the synthetic-data toggle off in production `createConnectorFrame` exposes a `synthetic` option that surfaces Monad's internal synthetic-data toggle. It defaults to `false` — leave it off in production; it's for testing only. ## Catalog performance - **Keep a server-side allow-list of the `type_id`s you support.** There are 300+ connector types; you rarely surface all of them. - **Cache the connector-type catalog.** Types are global and change rarely. - **Refresh a tenant's configured connectors only after a mutation** (connect or delete), not on every render. - **Enrich status server-side.** Listing configured connectors with their state does one `findIntegrationPipeline` call each. A handful is fine from the browser, but at scale fold it into your `/api/connectors` route so the browser gets one ready list. ## Session and theming performance - **Reuse session tokens across mounts** rather than minting one per iframe (see [Authentication](/sdks/embed/auth-tenancy#scope-lifetime-and-reuse)). - **Remember the remount cost.** `appearance` applies once at mount, so live re-theming means `destroy()` and remount. Debounce rapid changes if you build a theme customizer. ## Deployment - **One API key, one team per customer, mapping on your server.** Nothing new happens at deploy time except volume — provision a team as each customer onboards and keep the tenant-to-team record with your account data. - **Serve over HTTPS in production.** - **Use the production endpoints by default:** `apiBase` is `https://app.monad.com/api` and `frameOrigin` is `https://app.monad.com/embed`. Override them only for non-prod testing. - **A `403` on session mint in production** is usually a stale or mis-signed API key — re-issue it. - **A common local-dev snag:** `POST /v3/sessions` can `403` when a WAF blocks the `parentOrigin=http://localhost…` parameter. Point `frameOrigin` at a local UI dev server, or tunnel your host over HTTPS. ## Next - [API Reference](/sdks/embed/reference) — full options, theming tokens, catalog reads, connector icons, and the backend route contract. ## Support For additional support contact support@monad.com