Blog

How to migrate from Composio to Nango

A complete guide to migrating API integrations from Composio to Nango.

Sapnesh Naik
Sapnesh Naik
Developer Advocate
Building with AI
May 26, 2026
Copy URL

This is a migration guide for developers moving integrations from Composio to Nango. It maps every Composio feature to its Nango equivalent and shows the migration steps for each piece. Pair Claude Code (or Cursor, Codex, or any of the 18+ supported coding agents) with the Nango AI Function Builder skill and most of the porting happens on autopilot: the agent reads the upstream API, writes the function, tests it against a real connection with nango dryrun, and iterates on real responses.

Note: For a higher-level comparison, see Composio vs Nango.

Prerequisites

  • A Nango account. The free tier covers the migration.
  • Claude Code, Cursor, Codex or any other coding agent for generating integrations code.
  • For each integration you plan to migrate: your own OAuth client ID and secret. If you used Composio’s custom OAuth (bring-your-own client), you can reuse the same credentials in Nango.

Migration order (high-level)

  1. Pause new Composio work. New tools and triggers go on Nango from day one.
  2. Set up the Nango project (coding agent setup, quickstart) and create one test connection per integration from the Nango dashboard.
  3. Update the OAuth redirect URI on each provider’s OAuth app from Composio’s https://backend.composio.dev/api/v1/auth-apps/add callback to https://api.nango.dev/oauth/callback.
  4. Migrate auth: Composio connected_accounts.link() → Nango Connect UI (auth guide).
  5. Migrate tool calls: Composio tools → Nango action functions (action functions).
  6. Swap the MCP URL or REST base URL in your agent (tool calling).
  7. Migrate triggers: Composio triggers → Nango webhook-driven syncs (webhook functions).
  8. Add data syncs (net-new on Nango): durable polling syncs for RAG context and ingestion (sync functions).
  9. Move per-customer config into Nango connection metadata (metadata guide). (May not apply to all migrations.)
  10. Cut over: run side by side behind a feature flag, diff the logs, then rotate the Composio API key and cancel at the renewal boundary.

Mapping Composio features to Nango

Composio’s v3 SDK renamed several primitives. The labels below match the current Composio docs.

ComposioNangoNotes
Toolkit (e.g. `github`)Integration`provider_config_key` in Nango.<br />https://app.nango.dev/dev/integrations
Auth Config (`ac_xxx`)Integration settingsOAuth client ID, secret, and scopes per integration
Connected Account (`ca_xxx`)ConnectionOne per end customer; identified by `connection_id`.<br />https://app.nango.dev/dev/connections
Tool (e.g. `GITHUB_CREATE_ISSUE`)Action function`createAction({...})` in your repo, deployed to Nango.
`user_id``end_user_id` tag on a connectionSet when the Connect session is created
`connected_accounts.link()`Connect session + `openConnectUI()`White-label by default
Custom OAuth (white-label)Integration settingsDefault on every integration; no separate config
`composio.tools.execute()``nango.triggerAction()` / `POST /action/trigger`Tool execution endpoint
Hosted MCP URL (`/v3/mcp/{server_id}`)`https://api.nango.dev/mcp`One URL; per-customer via `connection-id` header
Custom tool (in-memory)Action function on the runtimePersistent, version-controlled, tenant-isolated
Trigger (`ti_xxx`)`onWebhook` on a sync, or polling syncRuns on Nango's runtime
Provider plugin (`composio_openai_agents`, ...)REST + MCPFramework-agnostic
No equivalent`createSync({...})`Durable data syncs for RAG
No equivalentPolling sync with `frequency`Triggers for APIs without webhooks
No equivalentConnection metadata (`nango.setMetadata`)Per-customer JSON read inside functions
No equivalentEnterprise self-hostingHelm chart for Kubernetes

Step 1: Set up the Nango project

Follow the coding agent setup and quickstart for the latest instructions. In short:

npm install -g nango
nango init
npx skills add NangoHQ/skills -s building-nango-functions-locally
install nango ai skill coding agent

Add the Nango docs MCP server to your coding agent so it reads current API references at generation time. Restart the agent so the skill loads.

Set the dev API key in nango-integrations/.env:

NANGO_SECRET_KEY_DEV=<your-dev-key>

For each integration you plan to migrate, configure it in the Nango dashboardConfigure New Integration, then add one test connection under ConnectionsAdd Test Connection. The coding agent uses this connection to run nango dryrun against the live provider API while iterating on each function.

add hubspot test connection nango dashboard

Update the OAuth callback URL on the provider. When you bring your own OAuth client into Nango, the redirect URL registered on the provider’s developer portal (Salesforce, HubSpot, Google, etc.) must point at Nango’s callback: https://api.nango.dev/oauth/callback. Update it on each provider’s OAuth app config before real users connect.

Step 2: Migrate auth

Composio creates an auth config once per provider and links a connected account per end user:

# Composio
from composio import Composio

composio = Composio(api_key="...")
connection = composio.connected_accounts.link(
    user_id="user_123",
    auth_config_id="ac_xxx",
    callback_url="https://your-app.com/callback",
)
print(connection.redirect_url)

On Composio, the consent screen also shows “Composio wants to access your account”.

composio oauth consent screen instantly

On Nango, the equivalent is a short-lived Connect session token created on your backend, used by the frontend SDK. Follow the auth guide for full details:

// Backend
import { Nango } from '@nangohq/node';

const nango = new Nango({ secretKey: process.env.NANGO_API_KEY! });

api.post('/sessionToken', async (req, res) => {
  const { data } = await nango.createConnectSession({
    tags: {
      end_user_id: '<END-USER-ID>',
      end_user_email: '<END-USER-EMAIL>',
      organization_id: '<ORGANIZATION-ID>'
    },
    allowed_integrations: ['<INTEGRATION-ID>']
  });

  res.status(200).send({ sessionToken: data.token });
});
// Frontend
import Nango from '@nangohq/frontend';

const nango = new Nango();
const connect = nango.openConnectUI({
  onEvent: (event) => {
    if (event.type === 'close') {
      // Modal closed.
    } else if (event.type === 'connect') {
      // Auth flow successful.
    }
  },
});

const res = await fetch('/sessionToken', { method: 'POST' });
connect.setSessionToken(res.sessionToken); // A loading indicator shows until this is set.

The consent screen on the upstream provider now shows your OAuth app. connection_id replaces connected_account_id as your handle for every subsequent call. Token refresh, encryption, and concurrency-safe refresh are handled by the runtime.

nango connect ui link salesforce

Note: OAuth refresh tokens are bound to the OAuth client that issued them. Your users must re-authorize via Connect UI after the migration to Nango.

Tip: For a fully custom UI, use nango.auth(...) from the frontend SDK (headless).

Step 3: Migrate tool calls

On Composio, an agent fetches tools from a toolkit catalog:

# Composio
from composio import Composio
from composio_openai_agents import OpenAIAgentsProvider

composio = Composio(provider=OpenAIAgentsProvider())
tools = composio.tools.get(
    user_id="user_123",
    toolkits=["GITHUB", "LINEAR"],
    limit=20,
)

With Nango, the equivalent is called Actions (exposed as tool-calls to your AI Agent).

Check Nango’s pre-built actions and syncs first. Each integration ships with open-source pre-built functions you can enable from the dashboard in one click, or clone into your repo with nango clone and customize. For example, Salesforce ships pre-built actions for create-record, update-record, upsert-record, and more. Browse the integrations catalog for the full list; the full source lives in the integration-templates repo.

nango dashboard salesforce pre built actions

If a pre-built action covers the tool you need, enable it and skip ahead to Step 4. Build a custom action only when the pre-built ones do not fit your use case.

Each custom tool is a TypeScript action function in your repo, written by the AI Function Builder skill against the action functions reference. Open the project in your coding agent and invoke the skill with the tool intent and the upstream endpoint:

/building-nango-functions-locally Build a Salesforce action that creates a contact from firstName, lastName, email, and accountId.

The agent researches the Salesforce API, writes the action with Zod input/output schemas, runs nango dryrun against your test connection, reads the real Salesforce response from the logs, and iterates until the action returns the expected shape.

The generated sample nango-integrations/salesforce/actions/create-contact.ts:

import { createAction } from 'nango';
import { z } from 'zod';

const Input = z.object({
    firstName: z.string().optional(),
    lastName: z.string(),
    email: z.string().optional(),
    accountId: z.string().optional(),
});
const Output = z.object({ id: z.string() });

export default createAction({
    description: 'Create a Salesforce Contact.',
    version: '1.0.0',
    input: Input,
    output: Output,
    exec: async (nango, input) => {
        const res = await nango.post({
            endpoint: '/services/data/v60.0/sobjects/Contact',
            data: {
                FirstName: input.firstName,
                LastName: input.lastName,
                Email: input.email,
                AccountId: input.accountId,
            },
        });
        return { id: res.data.id };
    },
});

Deploy only this action to your dev environment (see deployment options):

nango deploy dev

The skill handles pagination, auth headers, rate limits, and response paths so the agent does not stumble on the mechanical parts.

You never pass tokens directly. The runtime resolves credentials per request from provider-config-key + connection-id and refreshes them safely under concurrency.

Step 4: Swap the MCP URL

Composio uses one MCP URL per pre-configured server, with the user identified in the query string:

https://backend.composio.dev/v3/mcp/{SERVER_ID}?user_id={USER_ID}
x-api-key: <COMPOSIO-API-KEY>

Nango exposes one MCP endpoint:

https://api.nango.dev/mcp
Authorization: Bearer <NANGO-SECRET-KEY>
connection-id: <CONNECTION-ID>
provider-config-key: <INTEGRATION-ID>

Per-customer scoping comes from request headers, not from a per-customer URL. Every deployed action is automatically a tool, with input and output schemas derived from your Zod definitions.

Example MCP client config (Claude Code, Cursor, any MCP-aware agent):

{
  "mcpServers": {
    "nango": {
      "url": "https://api.nango.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${NANGO_SECRET_KEY}",
        "connection-id": "${USER_CONNECTION_ID}",
        "provider-config-key": "github"
      }
    }
  }
}

Or, use the agent’s CLI if available:

claude mcp add --transport http nango https://api.nango.dev/mcp \
  --header "Authorization: Bearer <NANGO-SECRET-KEY>" \
  --header "connection-id: <CONNECTION-ID>" \
  --header "provider-config-key: <INTEGRATION-ID>"
claude code mcp nango tools loaded

For stdio-only MCP clients, bridge through mcp-remote.

If your agent consumes tools via REST APIs instead of MCP (LangChain, OpenAI Agents SDK, Vercel AI SDK, Anthropic, Mastra), fetch OpenAI-shaped schemas through the /scripts/config endpoint:

curl --location 'https://api.nango.dev/scripts/config?format=openai' \
--header 'Authorization: Bearer <NANGO-SECRET-KEY>'
nango scripts config openai response

Then call any action:

curl --location 'https://api.nango.dev/action/trigger' \
--header 'Authorization: Bearer <NANGO-SECRET-KEY>' \
--header 'Connection-Id: <CONNECTION-ID>' \
--header 'Provider-Config-Key: <INTEGRATION-ID>' \
--header 'Content-Type: application/json' \
--data '{ "action_name": "create-contact", "input": { "lastName": "Doe", "email": "jane@example.com" } }'

The framework code in your agent does not change. Replace the Composio provider with the framework’s standard tool/MCP integration pointed at Nango.

Step 5: Migrate triggers

Composio triggers are tied to a connected account and post upstream events to a Composio-hosted webhook ingress, which forwards to your URL:

# Composio
trigger = composio.triggers.create(
    trigger_id="GITHUB_COMMIT_EVENT",
    connected_account_id="ca_xxx",
)

The Composio trigger catalog is narrow (Slack, Gmail, GitHub, Notion, a few others), and there is no polling trigger for APIs without webhooks.

On Nango, the equivalent is a webhook-driven sync. Nango ingests the upstream event, attributes it to a connection, and runs your onWebhook handler. The same sync runs on a schedule as a safety net for missed webhooks. Full reference in the webhook functions guide; the real-time syncs guide covers combining webhooks and polling on the same primitive.

import { createSync } from 'nango';
import * as z from 'zod';

const Contact = z.object({ id: z.string(), email: z.string() });

export default createSync({
    description: 'Sync HubSpot contacts with real-time webhook updates',
    version: '1.0.0',
    frequency: 'every hour',
    autoStart: true,
    webhookSubscriptions: ['contact.creation', 'contact.propertyChange'], // <======
    models: { Contact },
    exec: async (nango) => {
        for await (const page of nango.paginate({ endpoint: '/crm/v3/objects/contacts' })) {
            await nango.batchSave(page, 'Contact');
        }
    },
    onWebhook: async (nango, payload) => {
        const contactId = payload.body.objectId;
        const res = await nango.get({ endpoint: `/crm/v3/objects/contacts/${contactId}` });
        await nango.batchSave([res.data], 'Contact');
    },
});

To get the agent to write this, include the webhook intent in the original prompt or run a follow-up session with the skill on the same file:

/building-nango-functions-locally Add webhook subscriptions to the contacts sync for contact.creation and contact.propertyChange. Inside onWebhook, GET the changed contact by ID and save it.

Webhook forwarding without ingestion. If you only need the raw event in your backend (no record persistence in Nango), Nango can forward the signed payload to your URL instead. See webhook forwarding. Verify with nango.verifyWebhookSignature(...).

For APIs without webhooks, use a polling sync, covered in the next step.

Step 6: Add data syncs (new on Nango)

Composio has no data sync primitive. On Nango, sync functions run on a schedule, save records into Nango’s record store, and survive process restarts via checkpoints. Use them for RAG context, ingesting customer data into your DB, or building a local mirror of records for fast agent reads.

Prompt the skill end-to-end. For example, a HubSpot contacts sync that handles portals over the 10k search cap:

/building-nango-functions-locally Build a Nango sync for HubSpot contacts
that runs every hour

The agent generates the sync, runs nango dryrun against your test connection, and iterates until records come back clean. The full generated source is in the Nango HubSpot template (and the same pattern for Salesforce, Slack, and every other integration lives under the integration-templates repo).

Read synced records from your backend with nango.listRecords({ providerConfigKey, connectionId, model, modifiedAfter }). For the full production walkthrough with checkpointing across millions of records and the HubSpot 10k-search-cap workaround, see the HubSpot contacts sync guide.

Deploy:

nango deploy dev

Once deployed, the sync runs on the schedule against every connected customer:

nango live sync

Minimum frequency is 'every 30 seconds'. Checkpointing, durable resumption, and rate-limit-aware retries are handled by the runtime.

Step 7: Per-customer config

Composio has no first-class store for per-customer configuration. Teams typically pass field mappings or feature flags in on every tool call.

Nango stores three things per connection (full reference in the metadata guide):

  • Tags for attribution: end_user_id, end_user_email, organization_id (set at session creation).
  • Connection config for provider-specific settings: subdomain, tenant ID, region.
  • Metadata for your app’s per-customer JSON: field mappings, folder IDs, feature flags.

Inside any function:

const metadata = await nango.getMetadata();
const fieldMapping = metadata.fieldMapping ?? {};

From your backend:

await nango.setMetadata('hubspot', connectionId, {
    fieldMapping: { 'custom_region__c': 'region' },
    syncFrequency: 'every 15 minutes',
});

The same action or sync can read different metadata per customer at execution time.

nango connection metadata dashboard

Step 8: Cut over

  1. Route a percentage of traffic to Nango behind a feature flag (by end_user_id or organization_id).
  2. Diff the logs. Nango’s observability dashboard records the full request and response for every call; compare against the equivalent Composio trace. Fix any issues with the AI Builder Skill if required.
  3. Increase traffic on Nango as confidence grows. Once the diff stays clean for several days, flip to 100%.
  4. Rotate the Composio API key, delete connected accounts, and disable Composio MCP servers.
  5. Cancel Composio at the renewal boundary.
Nango logs

Use the Nango Playground for interactive validation during cutover. Pick any action or sync, run it against any connection from the dashboard, and inspect the full execution log inline.

FAQ

Do I need to rewrite my agent to migrate from Composio to Nango?

In most cases, no. Framework integrations (LangChain, CrewAI, OpenAI Agents SDK, Vercel AI SDK, Mastra, Anthropic, Claude Code, Cursor) do not change. Swap the MCP URL or REST base URL, regenerate the tool functions with the Nango AI Function Builder skill, and cut traffic over behind a feature flag.

How do Composio triggers map to Nango?

Two options. For providers that send webhooks, define a sync with webhookSubscriptions and an onWebhook handler. For providers without webhooks, define a polling sync with a frequency. Both run on Nango’s durable runtime with checkpointing and retries.

Is self-hosting available?

Yes, on the Enterprise plan. Nango ships a Helm chart for Kubernetes and a Terraform module for AWS ECS, with support for AWS, GCP, and Azure. Bring your own Postgres via NANGO_DATABASE_URL. Composio does not offer self-hosting.

Ready to get started?

Ship the integrations your customers need — with 800+ APIs and infrastructure built for scale.

Custom needs?