Skip to main content
Actions are functions that run on Nango’s platform to read from or write to external APIs. Unlike syncs, actions are triggered explicitly and return a typed result synchronously (or asynchronously for batch workloads). Think of actions as “virtual endpoints” on external APIs that you define: a single create-contact action that works with Salesforce, HubSpot, Attio, and any other CRM through a unified interface.

When to use actions

Actions are ideal when you want to:
  • Create unified interfaces across different APIs (e.g., one create-contact that abstracts Salesforce, HubSpot, and Attio)
  • Write data to external APIs — creating records, sending messages, triggering workflows
  • Abstract multi-step operations into a single call (e.g., create an opportunity and link it to a contact and company in one request)
  • Enforce type safety for all interactions with external APIs

Key facts

  • Actions run in Nango’s infrastructure, scoped to a connection
  • Actions take a typed input and return a typed output — you control the code and data models
  • Actions run synchronously by default, returning results directly in the API response
  • Actions can also run asynchronously for batch workloads
  • Actions can call other actions to compose workflows
  • Actions can be exposed as LLM tools via Nango’s built-in MCP server
  • The output of an action cannot exceed 2 MB
  • All invocations and API calls are logged in the Nango dashboard

Build an action

Step 1 — Set up your integrations folder

If you don’t have a nango-integrations folder yet, follow the functions setup guide.

Step 2 — Start dev mode

nango dev
Keep this terminal open while you develop.

Step 3 — Create the action file

Action files live inside an actions/ folder nested under the integration folder. For an action that fetches available fields on the Salesforce Contact object:
nango-integrations/
├── .nango
├── .env
├── index.ts
├── package.json
└── salesforce/
    └── actions/
        └── salesforce-contact-fields.ts
Paste the following scaffold:
salesforce-contact-fields.ts
import { createAction } from 'nango';
import * as z from 'zod';

export default createAction({
    description: '<Description of your action>',
    version: '1.0.0',
    input: z.void(),
    output: z.object({
        id: z.string(),
        // add more fields here
    }),
    exec: async (nango) => {
        // Integration code goes here.
    },
});
Also import the new file in index.ts:
index.ts
import './salesforce/actions/salesforce-contact-fields';

Step 4 — Implement your action

Write the logic in the exec method. Here is a complete action that fetches available fields on the Salesforce Contact object:
salesforce-contact-fields.ts
import { createAction } from 'nango';
import * as z from 'zod';

export default createAction({
    description: 'Fetches available contact fields from Salesforce',
    version: '1.0.0',
    input: z.void(),
    output: z.object({
        fields: z.array(z.object({
            name: z.string(),
            label: z.string(),
            type: z.string(),
            relationshipName: z.string().nullable()
        }))
    }),
    exec: async (nango) => {
        const response = await nango.get({
            endpoint: '/services/data/v51.0/sobjects/Contact/describe'
        });

        await nango.log('Salesforce fields fetched!');

        return {
            fields: response.data.fields.map((field: any) => ({
                name: field.name,
                label: field.label,
                type: field.type,
                relationshipName: field.relationshipName as string | null
            }))
        };
    }
});

SDK methods available in actions

MethodDescription
nango.get(config)Authenticated GET request
nango.post(config)Authenticated POST request
nango.put(config)Authenticated PUT request
nango.patch(config)Authenticated PATCH request
nango.delete(config)Authenticated DELETE request
nango.log(message)Write a custom log to the Nango dashboard
nango.getMetadata()Read per-connection metadata
nango.setMetadata(data)Write per-connection metadata
nango.triggerAction(key, id, name, input)Call another action from within this action
nango.paginate(config)Iterate through paginated API responses

Step 5 — Test locally

nango dryrun salesforce-contact-fields '<CONNECTION-ID>'
To pass input data:
nango dryrun salesforce-contact-fields '<CONNECTION-ID>' --input '{"field": "value"}'
Run nango dryrun --help to see all options.

Step 6 — Deploy

nango deploy dev
To deploy a single action:
nango deploy --action salesforce-contact-fields dev

Use an action from your backend

Triggering an action synchronously

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

const nango = new Nango({ secretKey: '<ENV-SECRET-KEY>' });

const result = await nango.triggerAction(
    'salesforce',        // providerConfigKey (integration ID)
    'user-123',          // connectionId
    'salesforce-contact-fields',  // actionName
    {}                   // optional input
);

console.log(result);
The response body is the typed output of your action, returned immediately.

Triggering an action asynchronously

Asynchronous execution is ideal for batch writes or workloads where you want Nango to handle rate limits and retries transparently without blocking your request.
// Trigger asynchronously — returns immediately with an ID and status URL
const { id, statusUrl } = await nango.triggerActionAsync(
    'salesforce',
    'user-123',
    'bulk-create-contacts',
    { contacts: [...] }
);

// Poll for the result
const result = await nango.getAsyncActionResult({ id });
Nango can also send a webhook to your backend when an async action finishes.

Action workflows

Actions can call other actions using nango.triggerAction() from within the exec method. Each invocation appears separately in the Nango logs.
exec: async (nango) => {
    // Call another action as part of a workflow
    const contact = await nango.triggerAction(
        nango.providerConfigKey,
        nango.connectionId,
        'get-contact',
        { email: '[email protected]' }
    );

    await nango.post({
        endpoint: '/opportunities',
        data: { contact_id: contact.id, name: 'New Deal' }
    });
}

Action configuration reference

FieldTypeDescription
descriptionstringHuman-readable description of the action
versionstringSemantic version; increment on breaking changes
inputz.ZodTypeZod schema for the action’s input (use z.void() for no input)
outputz.ZodTypeZod schema for the action’s return value
execfunctionThe action implementation

Build docs developers (and LLMs) love