Set up the Nango CLI and integration folder

Install the Nango CLI globally:
npm install -g nango

Create your Nango integrations folder

All your integrations live in a folder called nango-integrations. Whether located in your main codebase or a dedicated repository, this folder should be version-controlled. To initialize your integrations folder (e.g. at the root of your repository), run:
nango init nango-integrations
This creates the ./nango-integrations folder with some initial configuration and an example sync script. The nango-integrations directory looks like this:
nango-integrations/
├── .nango
├── .env
├── index.ts
├── package.json
└── demo-github-integration # this is the integration unique ID and must match an integration ID in your Nango UI
    └── syncs/
        └── github-issue-example.ts

Authenticate the CLI

In an .env file in ./nango-integrations, add the following environment variables:
NANGO_SECRET_KEY_PROD='<PROD-SECRET-KEY>'
NANGO_SECRET_KEY_DEV='<DEV-SECRET-KEY>'
Get your secret keys from the Environment Settings tab (toggle between the Production and Development environment in the left nav bar). Learn more about the Nango CLI (reference).

Write an integration script

Start script development mode

Before you plan to modify your integration scripts, run:
nango dev # Keep the tab open
This command starts a process that continuously compiles your integration scripts and prints code syntax errors.

Write a sync script

Create a sync script in ./nango-integrations/salesforce/syncs/.
salesforce-contacts.ts
import { createSync } from 'nango';

export default createSync({
  exec: async (nango) => {
    // Integration code goes here.
  },
});
Fill metadata and the exec method with your integration code. In the example here, we fetch tasks from Salesforce:
salesforce-contacts.ts
import { createSync } from 'nango';
import { z } from 'zod';

const SalesforceContact = z.object({
  id: z.string(),
  first_name: z.string(),
  last_name: z.string(),
  email: z.string(),
});

export default createSync({
  description: `Fetches contacts from Salesforce`,
  version: '1.0.0',
  endpoints: [{ method: 'GET', path: '/salesforce/contacts', group: 'Contacts' }],
  frequency: 'every hour',
  autoStart: true,
  syncType: 'full',
  trackDeletes: true,
  models: {
    SalesforceContact: SalesforceContact,
  },
  exec: async (nango) => {
    const query = buildQuery(nango.lastSyncDate);
    await fetchAndSaveRecords(nango, query);
    await nango.log('Sync run completed!');
  },
});
export type NangoSyncLocal = Parameters<(typeof sync)['exec']>[0];

function buildQuery(lastSyncDate?: Date): string {
    let baseQuery = `SELECT Id, FirstName, LastName, Email, AccountId, LastModifiedDate FROM Contact`;

    if (lastSyncDate) { // Only fetch the new data.
        baseQuery += ` WHERE LastModifiedDate > ${lastSyncDate.toISOString()}`;
    }

    return baseQuery;
}

async function fetchAndSaveRecords(nango: NangoSyncLocal, query: string) {
    let endpoint = '/services/data/v53.0/query';

    while (true) {
        const response = await nango.get({
            endpoint: endpoint,
            params: endpoint === '/services/data/v53.0/query' ? { q: query } : {}
        });

        const mappedRecords = mapContacts(response.data.records);

        await nango.batchSave(mappedRecords, 'SalesforceContact'); // Saves records to Nango cache.

        if (response.data.done) {
            break;
        }

        endpoint = response.data.nextRecordsUrl;
    }
}

function mapContacts(records: any[]): SalesforceContact[] {
    return records.map((record: any) => {
        return {
            id: record.Id as string,
            first_name: record.FirstName,
            last_name: record.LastName,
            email: record.Email,
            account_id: record.AccountId,
            last_modified_date: record.LastModifiedDate
        };
    });
}
In this integration script, the following Nango utilities are used:
  • nango.lastSyncDate is the last date at which the sync has run
  • await nango.batchSave() to persist external data in Nango’s cache
  • await nango.get() to perform an API request (automatically authenticated by Nango)
  • await nango.log() to print console logs (replaces console.log())
Learn more about sync scripts: understanding syncs, script reference, example templates.
Import it in your index.ts file:
index.ts
import './salesforce/syncs/salesforce-contacts';

Write an action script

Create an action script in ./nango-integrations/salesforce/actions/.
salesforce-contact-fields.ts
import { createAction } from 'nango';

export default createAction({
  exec: async (nango) => {
    // Integration code goes here.
  },
});
Fill metadata and the exec method with your integration code. In the example here, we fetch available contact fields from Salesforce:
salesforce-contact-fields.ts
import { createAction } from 'nango';

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()
    })),
  }),
  exec: async (nango) => {
    const response = await nango.get({
        endpoint: '/services/data/v51.0/sobjects/Contact/describe'
    });

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

    const { data } = response;
    const { fields, childRelationships } = data;

    return {
        fields: mapFields(fields)
    };
}

function mapFields(fields: any) {
    return fields.map((field) => {
        const { name, label, type, relationshipName } = field;
        return {
            name,
            label,
            type,
            relationshipName: relationshipName as string
        };
    });
}
In this integration script, the following Nango utilities are used:
  • await nango.get() to perform an API request (automatically authenticated by Nango)
  • await nango.log() to print console logs (replaces console.log())
  • return will synchronously return results from the action trigger request
Learn more about action scripts: understanding actions, script reference, example templates.
Import it in your index.ts file:
index.ts
import './salesforce/actions/salesforce-contact-fields';

Test your integration scripts locally

Easily test your integration scripts locally as you develop them with the dryrun function of the CLI (reference):
nango dryrun salesforce-contacts '<CONNECTION-ID>' # Sync
nango dryrun salesforce-contact-fields '<CONNECTION-ID>' # Action
nango dryrun --help # Display help for command
Because this is a dry run, syncs won’t persist data in Nango (and actions never persist data); instead, the retrieved data will be printed to the console.
By default, dryrun retrieves connections from your Dev environment.

Deploy your integration scripts

Nango provides multiple cloud environments so you can test your integration scripts more thoroughly before releasing them to customers. To deploy all integration scripts at once, run (reference):
nango deploy dev # Deploy to your Development environment
# or
nango deploy prod # Deploy to your Production environment
In the Nango UI, navigate to the Endpoints tab of the relevant integration(s) to verify the deployment succeeded.
Questions, problems, feedback? Please reach out in the Slack community.