Nango Integrations Best Practices — Cursor Rules | Neura Market
    Neura MarketNeura Market/Cursor
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsBlogVideosGuidesCoursesCommunityExtensionsTrendingGenerate
    CursorRulesNango Integrations Best Practices
    Back to Rules
    Backend

    Nango Integrations Best Practices

    April 15, 2026
    0 copies 0 downloads

    description: nango-integrations best practice rules for integration files

    Rule Content
    ---
    description: nango-integrations best practice rules for integration files
    glob: nango-integrations/*
    ruleType: always
    alwaysApply: true
    ---
    
    # Persona
    
    You are a top tier integrations engineer. You are methodical, pragmatic and systematic in how you write integration scripts. You follow best practices and look carefully at existing patterns and coding styles in this existing project. You will always attempt to test your work by using the "dryrun" command, and will use a connection if provided to test or will discover a valid connection by using the API to fetch one. You always run the available commands to ensure your work compiles, lints successfully and has a valid nango.yaml.
    
    ## Configuration - nango.yaml
    
    - If `sync_type: full`, then the sync should also have `track_deletes: true`
    - If the sync requires metadata, then the sync should be set to `auto_start: false`. The metadata should be documented as an input in the nango.yaml
    - Scopes should be documented
    - For optional properties in models, use the `?` suffix after the property name
    - Endpoints should be concise and simple, not necessarily reflecting the exact third-party API path
    - Model names and endpoint paths should not be duplicated within an integration
    - When adding a new integration, take care to not remove unrelated entries in the nango.yaml
    - For enum values in models, do not use quotes around the values
    
    ### Endpoint Naming Guidelines
    
    Keep endpoint definitions simple and consistent:
    
    ```yaml
    # ✅ Good: Simple, clear endpoint definition
    endpoint:
        method: PATCH
        path: /events
        group: Events
    
    # ❌ Bad: Overly specific, redundant path
    endpoint:
        method: PATCH
        path: /google-calendars/custom/events/{id}
        group: Events
    
    # ✅ Good: Clear resource identification
    endpoint:
        method: GET
        path: /users
        group: Users
    
    # ❌ Bad: Redundant provider name and verbose path
    endpoint:
        method: GET
        path: /salesforce/v2/users/list/all
        group: Users
    ```
    
    ```yaml
    integrations:
        hubspot:
            contacts:
                runs: every 5m
                sync_type: full
                track_deletes: true
                input: ContactMetadata
                auto_start: false
                scopes:
                    - crm.objects.contacts.read
                description: A super informative and helpful description that tells us what the sync does.
                endpoint:
                    method: GET
                    path: /contacts
                    group: Contacts
    models:
        ContactMetadata:
            # Required property
            name: string
            # Optional property using ? suffix
            cursor?: string
            # Optional property with union type
            # Enum values without quotes
            type?: user | admin
            status: ACTIVE | INACTIVE
            employmentType: FULL_TIME | PART_TIME | INTERN | OTHER
    ```
    
    ## Scripts
    
    ### General Guidelines
    
    - Use comments to explain the logic and link to external API documentation
    - Add comments with the endpoint URL above each API request
    - Avoid modifying arguments and prefer returning new values
    
    ### API Endpoints and Base URLs
    
    When constructing API endpoints, always check the official providers.yaml configuration at:
    [https://github.com/NangoHQ/nango/blob/master/packages/providers/providers.yaml](https://github.com/NangoHQ/nango/blob/master/packages/providers/providers.yaml)
    
    This file contains:
    - Base URLs for each provider
    - Authentication requirements
    - API version information
    - Common endpoint patterns
    - Required headers and configurations
    
    Example of using providers.yaml information:
    ```typescript
    const proxyConfig: ProxyConfiguration = {
        endpoint: '/v1/endpoint', // Path that builds on the `base_url` from the providers.yaml
        retries: 3,
        headers: {
            'Content-Type': 'application/json'
        }
    };
    ```
    
    ### Imports and Types
    
    - Add a `types.ts` file which contains typed third party API responses
      - Types in `types.ts` should be prefixed with the integration name (e.g., `GoogleUserResponse`, `AsanaTaskResponse`) as they represent the raw API responses
      - This helps avoid naming conflicts with the user-facing types defined in `nango.yaml`
    - Models defined in `nango.yaml` are automatically generated into a `models.ts` file
      - Always import these types from the models file instead of redefining them in your scripts
    - For non-type imports (functions, classes, etc.), always include the `.js` extension:
    
    ```typescript
    // ❌ Don't omit .js extension for non-type imports
    import { toEmployee } from '../mappers/to-employee';
    
    // ✅ Do include .js extension for non-type imports
    import { toEmployee } from '../mappers/to-employee.js';
    
    // ✅ Type imports don't need .js extension
    import type { TaskResponse } from '../../models';
    ```
    
    - Follow proper type naming and importing conventions:
    
    ```typescript
    // ❌ Don't define interfaces that match nango.yaml models
    interface TaskResponse {
        tasks: Task[];
    }
    
    // ✅ Do import types from the auto-generated models file
    import type { TaskResponse } from '../../models';
    
    // ❌ Don't use generic names for API response types
    interface UserResponse {
        // raw API response type
    }
    
    // ✅ Do prefix API response types with the integration name
    interface AsanaUserResponse {
        // raw API response type
    }
    ```
    
    ### API Calls and Configuration
    
    - Proxy calls should use retries:
      - Default for syncs: 10 retries
      - Default for actions: 3 retries
    
    ```typescript
    const proxyConfig: ProxyConfiguration = {
        retries: 10,
        // ... other config
    };
    ```
    
    - Use `await nango.log` for logging (avoid `console.log`)
    - Use the `params` property instead of appending params to the endpoint
    - Use the built-in `nango.paginate` wherever possible:
    
    ```typescript
    const proxyConfig: ProxyConfiguration = {
        endpoint,
        retries: 10,
        paginate: {
            response_path: 'comments'
        }
    };
    
    for await (const pages of nango.paginate(proxyConfig)) {
        // ... handle pages
    }
    ```
    
    - Always use `ProxyConfiguration` type when setting up requests
    - Add API documentation links above the endpoint property:
    
    ```typescript
    const proxyConfig: ProxyConfiguration = {
        // https://www.great-api-docs.com/endpoint
        endpoint,
        retries: 10,
    };
    ```
    
    ## Validation
    
    - Validate script inputs and outputs using `zod`
    - Validate and convert date inputs:
      - Ensure dates are valid
      - Convert to the format expected by the provider using `new Date`
      - Allow users to pass their preferred format
    - Use the nango zod helper for input validation:
    
    ```typescript
    const parseResult = await nango.zodValidateInput({
        zodSchema: documentInputSchema,
        input,
    });
    ```
    
    ## Syncs
    
    - `fetchData` must be the default export at the top of the file
    - Always paginate requests to retrieve all records
    - Avoid parallelizing requests (defeats retry policy and rate limiting)
    - Do not wrap syncs in try-catch blocks (Nango handles error reporting)
    - Use dedicated mapper functions for data transformation:
      - Place shared mappers in a `mappers` directory
      - Name files as `mappers/to-${entity}` (e.g., `mappers/to-employee.ts`)
    
    ```typescript
    import { toEmployee } from '../mappers/to-employee.js';
    
    export default async function fetchData(nango: NangoSync) {
        const proxyConfig: ProxyConfiguration = {
            endpoint: '/employees'
        };
        const allData = await nango.get(proxyConfig);
        return toEmployee(allData);
    }
    ```
    
    - Avoid type casting to leverage TypeScript benefits:
    
    ```typescript
    // ❌ Don't use type casting
    return {
        user: userResult.records[0] as HumanUser,
        userType: 'humanUser'
    };
    
    // ✅ Do use proper type checks
    if (isHumanUser(userResult.records[0])) {
        return {
            user: userResult.records[0],
            userType: 'humanUser'
        };
    }
    ```
    
    - For incremental syncs, use `nango.lastSyncDate`
    
    ## Actions
    
    - `runAction` must be the default export at the top of the file
    - Only use `ActionError` for specific error messages:
    
    ```typescript
    // ❌ Don't use generic Error
    throw new Error('Invalid response from API');
    
    // ✅ Do use nango.ActionError with a message
    throw new nango.ActionError({
        message: 'Invalid response format from API'
    });
    ```
    
    - Always return objects, not arrays
    - Always define API calls using a typed `ProxyConfiguration` object with retries set to 3:
    
    ```typescript
    // ❌ Don't make API calls without a ProxyConfiguration
    const { data } = await nango.get({
        endpoint: '/some-endpoint',
        params: { key: 'value' }
    });
    
    // ❌ Don't make API calls without setting retries for actions
    const proxyConfig: ProxyConfiguration = {
        endpoint: '/some-endpoint',
        params: { key: 'value' }
    };
    
    // ✅ Do use ProxyConfiguration with retries set to 3 for actions
    const proxyConfig: ProxyConfiguration = {
        endpoint: '/some-endpoint',
        params: { key: 'value' },
        retries: 3 // Default for actions is 3 retries
    };
    const { data } = await nango.get(proxyConfig);
    ```
    
    - When implementing pagination in actions, always return a cursor-based response to allow users to paginate through results:
    
    ```typescript
    // ✅ Define input type with optional cursor
    interface ListUsersInput {
        cursor?: string;
        limit?: number;
    }
    
    // ✅ Define response type with next_cursor
    interface ListUsersResponse {
        users: User[];
        next_cursor?: string; // undefined means no more results
    }
    
    // ✅ Example action implementation with pagination
    export default async function runAction(
        nango: NangoAction,
        input: ListUsersInput
    ): Promise<ListUsersResponse> {
        const proxyConfig: ProxyConfiguration = {
            endpoint: '/users',
            params: {
                limit: input.limit || 50,
                cursor: input.cursor
            },
            retries: 3
        };
        
        const { data } = await nango.get(proxyConfig);
        
        return {
            users: data.users,
            next_cursor: data.next_cursor // Pass through the API's cursor if available
        };
    }
    
    // ❌ Don't paginate without returning a cursor
    export default async function runAction(
        nango: NangoAction,
        input: ListUsersInput
    ): Promise<User[]> { // Wrong: Returns array without pagination info
        const { data } = await nango.get({
            endpoint: '/users',
            params: { cursor: input.cursor }
        });
        return data.users;
    }
    ```
    
    ```typescript
    // Complete action example:
    import type { NangoAction, ProxyConfiguration, FolderContentInput, FolderContent } from '../../models';
    import { folderContentInputSchema } from '../schema.zod.js';
    
    export default async function runAction(
        nango: NangoAction,
        input: FolderContentInput
    ): Promise<FolderContent> {
        const proxyConfig: ProxyConfiguration = {
            // https://api.example.com/docs/endpoint
            endpoint: '/some-endpoint',
            params: { key: 'value' },
            retries: 3 // Default for actions is 3 retries
        };
        
        const { data } = await nango.get(proxyConfig);
        return { result: data };
    }
    ```
    
    ## Testing
    
    In order to test you need a valid connectionId. You can programmatically discover a valid connection by using the Node SDK. Here's a complete example of finding Salesforce connections:
    
    1. First, create a script (e.g., `find-connections.js`):
    
    ```typescript
    import { Nango } from '@nangohq/node';
    import * as dotenv from 'dotenv';
    
    // Load environment variables from .env file
    dotenv.config();
    
    function findNangoSecretKey(): string {
        // Get all environment variables
        const envVars = process.env;
        
        // Find all NANGO_SECRET_KEY variables
        const nangoKeys = Object.entries(envVars)
            .filter(([key]) => key.startsWith('NANGO_SECRET_KEY'))
            .sort(([keyA], [keyB]) => {
                // Sort by specificity (env-specific keys first)
                const isEnvKeyA = keyA !== 'NANGO_SECRET_KEY';
                const isEnvKeyB = keyB !== 'NANGO_SECRET_KEY';
                if (isEnvKeyA && !isEnvKeyB) return -1;
                if (!isEnvKeyA && isEnvKeyB) return 1;
                return keyA.localeCompare(keyB);
            });
    
        if (nangoKeys.length === 0) {
            throw new Error('No NANGO_SECRET_KEY environment variables found');
        }
    
        // Use the first key after sorting
        const [key, value] = nangoKeys[0];
        console.log(`Using secret key: ${key}`);
        return value;
    }
    
    function isValidConnection(connection: any): boolean {
        // Connection is valid if:
        // 1. No errors array exists, or
        // 2. Errors array is empty, or
        // 3. No errors with type "auth" exist
        if (!connection.errors) return true;
        if (connection.errors.length === 0) return true;
        return !connection.errors.some(error => error.type === 'auth');
    }
    
    async function findConnections(providerConfigKey: string) {
        const secretKey = findNangoSecretKey();
        
        const nango = new Nango({ 
            secretKey 
        });
    
        // List all connections
        const { connections } = await nango.listConnections();
        
        // Filter for specific provider config key and valid connections
        const validConnections = connections.filter(conn => 
            conn.provider_config_key === providerConfigKey && 
            isValidConnection(conn)
        );
        
        if (validConnections.length === 0) {
            console.log(`No valid connections found for integration: ${providerConfigKey}`);
            return;
        }
    
        console.log(`Found ${validConnections.length} valid connection(s) for integration ${providerConfigKey}:`);
        validConnections.forEach(conn => {
            console.log(`- Connection ID: ${conn.connection_id}`);
            console.log(`  Provider: ${conn.provider}`);
            console.log(`  Created: ${conn.created}`);
            if (conn.errors?.length > 0) {
                console.log(`  Non-auth Errors: ${conn.errors.length}`);
            }
            console.log('---');
        });
    }
    
    // Find connections for the salesforce integration
    findConnections('salesforce').catch(console.error);
    ```
    
    2. Make sure your `.env` file contains at least one secret key:
    ```env
    # Environment-specific keys take precedence
    NANGO_SECRET_KEY_DEV=your_dev_secret_key_here
    NANGO_SECRET_KEY_STAGING=your_staging_secret_key_here
    # Fallback key
    NANGO_SECRET_KEY=your_default_secret_key_here
    ```
    
    3. Run the script:
    ```bash
    node find-connections.js
    ```
    
    Example output for the salesforce integration:
    ```
    Using secret key: NANGO_SECRET_KEY_DEV
    Found 1 valid connection(s) for integration salesforce:
    - Connection ID: 3374a138-a81c-4ff9-b2ed-466c86b3554d
      Provider: salesforce
      Created: 2025-02-18T08:41:24.156+00:00
      Non-auth Errors: 1
    ---
    ```
    
    Each connection in the response includes:
    - `connection_id`: The unique identifier you'll use for testing (e.g., "3374a138-a81c-4ff9-b2ed-466c86b3554d")
    - `provider`: The API provider (e.g., 'salesforce')
    - `provider_config_key`: The integration ID you searched for (e.g., 'salesforce')
    - `created`: Timestamp of when the connection was created
    - `end_user`: Information about the end user if available
    - `errors`: Any sync or auth errors associated with the connection (connections with auth errors are filtered out)
    - `metadata`: Additional metadata specific to the provider (like field mappings)
    
    
    ## Script Best Practices Checklist
    - [ ] nango.paginate is used to paginate over responses in a sync
    - [ ] if it is possible that an action could have a paginated response then the action should return back a `cursor` so the user can paginate over the action response## Integration Directory Structure
    
    Your integration should follow this directory structure for consistency and maintainability:
    
    ```
    nango-integrations/
    ├── nango.yaml              # Main configuration file
    ├── models.ts               # Auto-generated models from nango.yaml
    ├── schema.zod.ts          # Generated zod schemas for validation
    └── ${integrationName}/
        ├── types.ts           # Third-party API response types
        ├── actions/           # Directory for action implementations
        │   ├── create-user.ts
        │   ├── update-user.ts
        │   └── delete-user.ts
        ├── syncs/             # Directory for sync implementations
        │   ├── users.ts
        │   └── teams.ts
        └── mappers/          # Shared data transformation functions
            ├── to-user.ts
            └── to-team.ts
    ```
    
    ### Key Components
    
    1. **Root Level Files**:
       - `nango.yaml`: Main configuration file for all integrations
       - `models.ts`: Auto-generated models from nango.yaml. If this doesn't exist or you have updated the `nango.yaml` be sure to run `npx nango generate`
       - `schema.zod.ts`: Generated validation schemas
    
    2. **Integration Level Files**:
       - `types.ts`: Third-party API response types specific to the integration
    
    3. **Actions Directory**:
       - One file per action
       - Named after the action (e.g., `create-user.ts`, `update-user.ts`)
       - Each file exports a default `runAction` function
    
    4. **Syncs Directory**:
       - One file per sync
       - Named after the sync (e.g., `users.ts`, `teams.ts`)
       - Each file exports a default `fetchData` function
    
    5. **Mappers Directory**:
       - Shared data transformation functions
       - Named with pattern `to-${entity}.ts`
       - Used by both actions and syncs
    
    ### Running Tests
    
    Test scripts directly against the third-party API using dryrun:
    
    ```bash
    npx nango dryrun ${scriptName} ${connectionId} --integration-id ${INTEGRATION} --auto-confirm
    ```
    
    Example:
    ```bash
    npx nango dryrun settings g --integration-id google-calendar --auto-confirm
    ```
    
    ### Dryrun Options
    
    - `--auto-confirm`: Skip prompts and show all output
    ```bash
    npx nango dryrun settings g --auto-confirm --integration-id google-calendar
    ```
    
    
    ## Script Helpers
    
    -   `npx nango dryrun ${scriptName} ${connectionId} -e ${Optional environment}` --integration-id ${INTEGRATION}
    -   `npx nango compile` -- ensure all integrations compile
    -   `npx nango generate` -- when adding an integration or updating the nango.yaml this command should be run to update the models.ts file and also the schema auto-generated files
    -   `npx nango sync:config.check` -- ensure the nango.yaml is valid and could compile successfully 
    
    ## Deploying Integrations
    
    Once your integration is complete and tested, you can deploy it using the Nango CLI:
    
    ```bash
    npx nango deploy <environment>
    ```
    
    ### Deployment Options
    
    - `--auto-confirm`: Skip all confirmation prompts
    - `--debug`: Run CLI in debug mode with verbose logging
    - `-v, --version [version]`: Tag this deployment with a version (useful for rollbacks)
    - `-s, --sync [syncName]`: Deploy only a specific sync
    - `-a, --action [actionName]`: Deploy only a specific action
    - `-i, --integration [integrationId]`: Deploy all scripts for a specific integration
    - `--allow-destructive`: Allow destructive changes without confirmation (use with caution)
    
    ### Examples
    
    Deploy everything to production:
    ```bash
    npx nango deploy production
    ```
    
    Deploy a specific sync to staging:
    ```bash
    npx nango deploy staging -s contacts
    ```
    
    Deploy an integration with version tag:
    ```bash
    npx nango deploy production -i salesforce -v 1.0.0
    ```
    
    Deploy with auto-confirmation:
    ```bash
    npx nango deploy staging --auto-confirm
    ```
    
    
    ## Full Example of a sync and action in nango
    
    Here's a complete example of a GitHub integration that syncs pull requests and has an action to create a pull request:
    
    `nango-integrations/nango.yaml`:
    ```yaml
    integrations:
        github:
            syncs:
                pull-requests:
                    runs: every hour
                    description: |
                        Get all pull requests from a Github repository.
                    sync_type: incremental
                    endpoint:
                        method: GET
                        path: /pull-requests
                        group: Pull Requests
                    input: GithubMetadata
                    output: PullRequest
                    auto_start: false
                    scopes:
                        - repo
                        - repo:status
            actions:
                create-pull-request:
                    description: Create a new pull request
                    endpoint:
                        method: POST
                        path: /pull-requests
                        group: Pull Requests
                    input: CreatePullRequest
                    output: PullRequest
                    scopes:
                        - repo
                        - repo:status
    
    models:
        GithubMetadata:
            owner: string
            repo: string
        CreatePullRequest:
            owner: string
            repo: string
            title: string
            head: string
            base: string
            body?: string
        PullRequest:
            id: number
            number: number
            title: string
            state: string
            body?: string
            created_at: string
            updated_at: string
            closed_at?: string
            merged_at?: string
            head:
                ref: string
                sha: string
            base:
                ref: string
                sha: string
    ```
    
    `nango-integrations/github/types.ts`:
    ```typescript
    export interface GithubPullRequestResponse {
        id: number;
        number: number;
        title: string;
        state: string;
        body: string | null;
        created_at: string;
        updated_at: string;
        closed_at: string | null;
        merged_at: string | null;
        head: {
            ref: string;
            sha: string;
        };
        base: {
            ref: string;
            sha: string;
        };
    }
    ```
    
    `nango-integrations/github/mappers/to-pull-request.ts`:
    ```typescript
    import type { PullRequest } from '../../models';
    import type { GithubPullRequestResponse } from '../types';
    
    export function toPullRequest(response: GithubPullRequestResponse): PullRequest {
        return {
            id: response.id,
            number: response.number,
            title: response.title,
            state: response.state,
            body: response.body || undefined,
            created_at: response.created_at,
            updated_at: response.updated_at,
            closed_at: response.closed_at || undefined,
            merged_at: response.merged_at || undefined,
            head: {
                ref: response.head.ref,
                sha: response.head.sha
            },
            base: {
                ref: response.base.ref,
                sha: response.base.sha
            }
        };
    }
    ```
    
    `nango-integrations/github/syncs/pull-requests.ts`:
    ```typescript
    import type { NangoSync, ProxyConfiguration, GithubMetadata } from '../../models';
    import type { GithubPullRequestResponse } from '../types';
    import { toPullRequest } from '../mappers/to-pull-request.js';
    
    export default async function fetchData(
        nango: NangoSync
    ): Promise<void> {
        // Get metadata containing repository information
        const metadata = await nango.getMetadata<GithubMetadata>();
        
        const proxyConfig: ProxyConfiguration = {
            // https://docs.github.com/en/rest/pulls/pulls#list-pull-requests
            endpoint: `/repos/${metadata.owner}/${metadata.repo}/pulls`,
            params: {
                state: 'all',
                sort: 'updated',
                direction: 'desc'
            },
            retries: 10
        };
    
        // Use paginate to handle GitHub's pagination
        for await (const pullRequests of nango.paginate<GithubPullRequestResponse[]>(proxyConfig)) {
            const mappedPRs = pullRequests.map(toPullRequest);
            await nango.batchSave(mappedPRs);
        }
    }
    ```
    
    `nango-integrations/github/actions/create-pull-request.ts`:
    ```typescript
    import type { NangoAction, ProxyConfiguration, PullRequest, CreatePullRequest } from '../../models';
    import type { GithubPullRequestResponse } from '../types';
    import { toPullRequest } from '../mappers/to-pull-request.js';
    
    export default async function runAction(
        nango: NangoAction,
        input: CreatePullRequest
    ): Promise<PullRequest> {
        // https://docs.github.com/en/rest/pulls/pulls#create-a-pull-request
        const proxyConfig: ProxyConfiguration = {
            endpoint: `/repos/${input.owner}/${input.repo}/pulls`,
            data: {
                title: input.title,
                head: input.head,
                base: input.base,
                body: input.body
            },
            retries: 3
        };
    
        const { data } = await nango.post<GithubPullRequestResponse>(proxyConfig);
        return toPullRequest(data);
    }
    ```
    
    This example demonstrates:
    1. A well-structured `nango.yaml` with models, sync, and action definitions
    2. Proper type definitions for the GitHub API responses
    3. A reusable mapper function for data transformation
    4. An incremental sync that handles pagination and uses `getMetadata()`
    5. An action that creates new pull requests
    6. Following all best practices for file organization and code structure# Advanced Integration Script Patterns
    
    This guide covers advanced patterns for implementing different types of Nango integration syncs. Each pattern addresses specific use cases and requirements you might encounter when building integrations.
    
    ## Table of Contents
    
    1. [Configuration Based Sync](#configuration-based-sync)
    2. [Selection Based Sync](#selection-based-sync)
    3. [Window Time Based Sync](#window-time-based-sync)
    4. [Action Leveraging Sync Responses](#action-leveraging-sync-responses)
    5. [24 Hour Extended Sync](#24-hour-extended-sync)
    
    ## Configuration Based Sync
    
    ### Overview
    A configuration-based sync allows customization of the sync behavior through metadata provided in the nango.yaml file. This pattern is useful when you need to:
    - Configure specific fields to sync
    - Set custom endpoints or parameters
    - Define filtering rules
    
    ### Key Characteristics
    - Uses metadata in nango.yaml for configuration
    - Allows runtime customization of sync behavior
    - Supports flexible data mapping
    - Can handle provider-specific requirements
    
    ### Implementation Notes
    
    This pattern leverages metadata to define a dynamic schema that drives the sync. The implementation typically consists of two parts:
    
    1. An action to fetch available fields using the provider's introspection endpoint
    2. A sync that uses the configured fields to fetch data
    
    Example configuration in `nango.yaml`:
    
    ```yaml
    integrations:
        salesforce:
            configuration-based-sync:
                sync_type: full
                track_deletes: true
                endpoint: GET /dynamic
                description: Fetch all fields of a dynamic model
                input: DynamicFieldMetadata
                auto_start: false
                runs: every 1h
                output: OutputData
    
    models:
        DynamicFieldMetadata:
            configurations: Configuration[]
        Configuration:
            model: string
            fields: Field[]
        Field:
            id: string
            name: string
            type: string
        OutputData:
            id: string
            model: string
            data:
                __string: any
    ```
    
    Example field introspection action:
    
    ```typescript
    export default async function runAction(
        nango: NangoAction,
        input: Entity,
    ): Promise<GetSchemaResponse> {
        const entity = input.name;
        
        // Query the API's introspection endpoint
        const response = await nango.get({
            endpoint: `/services/data/v51.0/sobjects/${entity}/describe`,
        });
        // ... process and return field schema
    }
    ```
    
    Example sync implementation:
    
    ```typescript
    import type { NangoSync, DynamicFieldMetadata, OutputData } from '../models.js';
    
    const SF_VERSION = 'v59.0';
    
    export default async function fetchData(
        nango: NangoSync,
        metadata: DynamicFieldMetadata
    ): Promise<void> {
        // Process each model configuration
        for (const config of metadata.configurations) {
            const { model, fields } = config;
            
            // Construct SOQL query with field selection
            const fieldNames = fields.map(f => f.name).join(',');
            const soqlQuery = `SELECT ${fieldNames} FROM ${model}`;
            
            // Query Salesforce API using SOQL
            const response = await nango.get({
                endpoint: `/services/data/${SF_VERSION}/query`,
                params: {
                    q: soqlQuery
                }
            });
    
            // Map response to OutputData format and save
            const mappedData = response.data.records.map(record => ({
                id: record.Id,
                model: model,
                data: fields.reduce((acc, field) => {
                    acc[field.name] = record[field.name];
                    return acc;
                }, {} as Record<string, any>)
            }));
    
            // Save the batch of records
            await nango.batchSave(mappedData);
        }
    }
    ```
    
    Key implementation aspects:
    - Uses metadata to drive the API queries
    - Dynamically constructs field selections
    - Supports multiple models from the third party API in a single sync
    - Maps responses to a consistent output format
    - Requires complementary action for field introspection
    - Supports flexible schema configuration through nango.yaml
    
    ## Selection Based Sync
    
    ### Overview
    A selection-based sync pattern allows users to specify exactly which resources to sync through metadata. This pattern is useful when you need to:
    - Sync specific files or folders rather than an entire dataset
    - Allow users to control the sync scope dynamically
    - Handle nested resources efficiently
    - Optimize performance by limiting the sync scope
    
    ### Key Characteristics
    - Uses metadata to define sync targets
    - Supports multiple selection types (e.g., files and folders)
    - Handles nested resources recursively
    - Processes data in batches
    - Maintains clear error boundaries
    
    ### Visual Representation
    
    ```mermaid
    graph TD
        A[Start] --> B[Load Metadata]
        B --> C[Process Folders]
        B --> D[Process Files]
        C --> E[List Contents]
        E --> F{Is File?}
        F -->|Yes| G[Add to Batch]
        F -->|No| E
        D --> G
        G --> H[Save Batch]
        H --> I[End]
    ```
    
    ### Implementation Example
    
    Here's how this pattern is implemented in a Box files sync:
    
    ```yaml
    # nango.yaml configuration
    files:
        description: Sync files from specific folders or individual files
        input: BoxMetadata
        auto_start: false
        sync_type: full
    
    models:
        BoxMetadata:
            files: string[]
            folders: string[]
        BoxDocument:
            id: string
            name: string
            modified_at: string
            download_url: string
    ```
    
    ```typescript
    export default async function fetchData(nango: NangoSync) {
        const metadata = await nango.getMetadata<BoxMetadata>();
        const files = metadata?.files ?? [];
        const folders = metadata?.folders ?? [];
        const batchSize = 100;
    
        if (files.length === 0 && folders.length === 0) {
            throw new Error('Metadata for files or folders is required.');
        }
    
        // Process folders first
        for (const folder of folders) {
            await fetchFolder(nango, folder);
        }
    
        // Then process individual files
        let batch: BoxDocument[] = [];
        for (const file of files) {
            const metadata = await getFileMetadata(nango, file);
            batch.push({
                id: metadata.id,
                name: metadata.name,
                modified_at: metadata.modified_at,
                download_url: metadata.shared_link?.download_url
            });
            if (batch.length >= batchSize) {
                await nango.batchSave(batch, 'BoxDocument');
                batch = [];
            }
        }
        if (batch.length > 0) {
            await nango.batchSave(batch, 'BoxDocument');
        }
    }
    
    async function fetchFolder(nango: NangoSync, folderId: string) {
        const proxy: ProxyConfiguration = {
            endpoint: `/2.0/folders/${folderId}/items`,
            params: {
                fields: 'id,name,modified_at,shared_link'
            },
            paginate: {
                type: 'cursor',
                response_path: 'entries'
            }
        };
    
        let batch: BoxDocument[] = [];
        const batchSize = 100;
    
        for await (const items of nango.paginate(proxy)) {
            for (const item of items) {
                if (item.type === 'folder') {
                    await fetchFolder(nango, item.id);
                }
                if (item.type === 'file') {
                    batch.push({
                        id: item.id,
                        name: item.name,
                        modified_at: item.modified_at,
                        download_url: item.shared_link?.download_url
                    });
                    if (batch.length >= batchSize) {
                        await nango.batchSave(batch, 'BoxDocument');
                        batch = [];
                    }
                }
            }
        }
    
        if (batch.length > 0) {
            await nango.batchSave(batch, 'BoxDocument');
        }
    }
    ```
    
    ### Best Practices
    1. **Simple Metadata Structure**: Keep the selection criteria simple and clear
    2. **Batch Processing**: Save data in batches for better performance
    3. **Clear Resource Types**: Handle different resource types (files/folders) separately
    4. **Error Boundaries**: Handle errors at the item level to prevent full sync failure
    5. **Progress Logging**: Add debug logs for monitoring progress
    
    ### Common Pitfalls
    1. Not validating metadata inputs
    2. Missing batch size limits
    3. Not handling API rate limits
    4. Poor error handling for individual items
    5. Missing progress tracking logs
    
    ## Window Time Based Sync
    
    ### Overview
    
    A window time based sync pattern is designed to efficiently process large datasets by breaking the sync into discrete, time-bounded windows (e.g., monthly or weekly). This approach is essential when:
    
    - The third-party API or dataset is too large to fetch in a single request or run.
    - You want to avoid timeouts, memory issues, or API rate limits.
    - You need to ensure incremental, resumable progress across large time ranges.
    
    This pattern is especially useful for financial or transactional data, where records are naturally grouped by time periods.
    
    ### Key Characteristics
    
    - Divides the sync into time windows (e.g., months).
    - Iterates over each window, fetching and processing data in batches.
    - Uses metadata to track progress and allow for resumable syncs.
    - Handles both initial full syncs and incremental updates.
    - Supports batching and pagination within each window.
    
    ### Visual Representation
    
    ```mermaid
    graph TD
        A[Start] --> B[Load Metadata]
        B --> C{More Windows?}
        C -->|Yes| D[Set Window Start/End]
        D --> E[Build Query for Window]
        E --> F[Get Count]
        F --> G[Batch Fetch & Save]
        G --> H[Update Metadata]
        H --> C
        C -->|No| I[Check for Incremental]
        I -->|Yes| J[Fetch Since Last Sync]
        J --> K[Batch Fetch & Save]
        K --> L[Done]
        I -->|No| L
    ```
    
    ### Implementation Example
    
    Here's a simplified example of the window time based sync pattern, focusing on the window selection and iteration logic:
    
    ```typescript
    export default async function fetchData(nango: NangoSync): Promise<void> {
        // 1. Load metadata and determine the overall date range
        const metadata = await nango.getMetadata();
        const lookBackPeriodInYears = 5;
        const { startDate, endDate } = calculateDateRange(metadata, lookBackPeriodInYears);
        let currentStartDate = new Date(startDate);
    
        // 2. Iterate over each time window (e.g., month)
        while (currentStartDate < endDate) {
            let currentEndDate = new Date(currentStartDate);
            currentEndDate.setMonth(currentEndDate.getMonth() + 1);
            currentEndDate.setDate(1);
    
            if (currentEndDate > endDate) {
                currentEndDate = new Date(endDate);
            }
    
            // 3. Fetch and process data for the current window
            const data = await fetchDataForWindow(currentStartDate, currentEndDate);
            await processAndSaveData(data);
    
            // 4. Update metadata to track progress
            await nango.updateMetadata({
                fromDate: currentEndDate.toISOString().split("T")[0],
                toDate: endDate.toISOString().split("T")[0],
                useMetadata: currentEndDate < endDate,
            });
    
            currentStartDate = new Date(currentEndDate.getTime());
            if (currentStartDate >= endDate) {
                await nango.updateMetadata({
                    fromDate: endDate.toISOString().split("T")[0],
                    toDate: endDate.toISOString().split("T")[0],
                    useMetadata: false,
                });
                break;
            }
        }
    
        // 5. Optionally, handle incremental updates after the full windowed sync
        if (!metadata.useMetadata) {
            // ... (incremental sync logic)
        }
    }
    
    async function fetchDataForWindow(start: Date, end: Date) {
        // Implement provider-specific logic to fetch data for the window
        return [];
    }
    
    async function processAndSaveData(data: any[]) {
        // Implement logic to process and save data
    }
    ```
    
    **Key implementation aspects:**
    
    - **Windowing:** The sync iterates over each month (or other time window), building queries and fetching data for just that period.
    - **Batching:** Large result sets are fetched in batches (e.g., 100,000 records at a time) within each window.
    - **Metadata:** Progress is tracked in metadata, allowing the sync to resume from the last completed window if interrupted.
    - **Incremental:** After the full windowed sync, the script can switch to incremental mode, fetching only records modified since the last sync.
    - **Error Handling:** Each window and batch is processed independently, reducing the risk of a single failure stopping the entire sync.
    
    ### Best Practices
    
    1. **Choose an appropriate window size** (e.g., month, week) based on data volume and API limits.
    2. **Track progress in metadata** to support resumability and avoid duplicate processing.
    3. **Batch large queries** to avoid memory and timeout issues.
    4. **Log progress** for observability and debugging.
    5. **Handle incremental updates** after the initial full sync.
    
    ### Common Pitfalls
    
    1. Not updating metadata after each window, risking duplicate or missed data.
    2. Using too large a window size, leading to timeouts or API errors.
    3. Not handling incremental syncs after the initial windowed sync.
    4. Failing to batch large result sets, causing memory issues.
    5. Not validating or handling edge cases in date calculations.
    
    ## Action Leveraging Sync Responses
    
    ### Overview
    
    An "Action Leveraging Sync Responses" pattern allows actions to efficiently return data that has already been fetched and saved by a sync, rather than always querying the third-party API. This approach is useful when:
    
    - The data needed by the action is already available from a previous sync.
    - You want to minimize API calls, reduce latency, and improve reliability.
    - You want to provide a fast, consistent user experience even if the third-party API is slow or unavailable.
    
    This pattern is especially valuable for actions that need to return lists of entities (e.g., users, projects, items) that are already available from a sync.
    
    ### Key Characteristics
    
    - Uses previously fetched or synced data when available.
    - Falls back to a live API call only if no data is available.
    - Transforms data as needed before returning.
    - Returns a consistent, typed response.
    
    ### Visual Representation
    
    ```mermaid
    graph TD
        A[Action Called] --> B[Check for Synced Data]
        B -->|Data Found| C[Return Synced Data]
        B -->|No Data| D[Fetch from API]
        D --> E[Transform/Return API Data]
    ```
    
    ### Implementation Example
    
    Here's a generic example of this pattern:
    
    ```typescript
    /**
     * Fetch all entities for an action, preferring previously synced data.
     * 1) Try using previously synced data (Entity).
     * 2) If none found, fallback to fetch from API.
     * 3) Return transformed entities.
     */
    export default async function runAction(nango: NangoAction) {
      const syncedEntities: Entity[] = await getSyncedEntities(nango);
    
      if (syncedEntities.length > 0) {
        return {
          entities: syncedEntities.map(({ id, name, ...rest }) => ({
            id,
            name,
            ...rest,
          })),
        };
      }
    
      // Fallback: fetch from API (not shown)
      return { entities: [] };
    }
    
    async function getSyncedEntities(nango: NangoAction): Promise<Entity[]> {
      // Implement logic to retrieve entities from previously synced data
      return [];
    }
    ```
    
    **Key implementation aspects:**
    
    - **Synced data first:** The action first attempts to use data that was previously fetched by a sync.
    - **Fallback:** If no records are found, it can fallback to a live API call (not shown in this example).
    - **Transformation:** The action transforms the data as needed before returning.
    - **Consistent Response:** Always returns a consistent, typed response, even if no data is found.
    
    ### Best Practices
    
    1. **Prefer previously synced data** to minimize API calls and improve performance.
    2. **Handle empty or special cases** gracefully.
    3. **Return a consistent response shape** regardless of data source.
    4. **Document fallback logic** for maintainability.
    5. **Keep transformation logic simple and clear.**
    
    ### Common Pitfalls
    
    1. Not keeping synced data up to date, leading to stale or missing data.
    2. Failing to handle the case where no data is available from sync or API.
    3. Returning inconsistent response shapes.
    4. Not transforming data as needed.
    5. Overcomplicating fallback logic.
    
    ## 24 Hour Extended Sync
    
    ### Overview
    A 24-hour extended sync pattern is designed to handle large datasets that cannot be processed within a single sync run due to Nango's 24-hour script execution limit. This pattern is essential when:
    - Your sync needs to process more data than can be handled within 24 hours
    - You need to handle API rate limits while staying within the execution limit
    - You're dealing with very large historical datasets
    - You need to ensure data consistency across multiple sync runs
    
    ### Why This Pattern?
    
    Nango enforces a 24-hour limit on script execution time for several reasons:
    - To prevent runaway scripts that could impact system resources
    - To ensure fair resource allocation across all integrations
    - To maintain system stability and predictability
    - To encourage efficient data processing patterns
    
    When your sync might exceed this limit, you need to:
    1. Break down the sync into manageable chunks
    2. Track progress using metadata
    3. Resume from where the last run stopped
    4. Ensure data consistency across runs
    
    ### Visual Representation
    
    ```mermaid
    graph TD
        A[Start Sync] --> B{Has Metadata?}
        B -->|No| C[Initialize]
        B -->|Yes| D[Resume]
        C --> E[Process Batch]
        D --> E
        E --> F{Check Status}
        F -->|Time Left| E
        F -->|24h Limit| G[Save Progress]
        F -->|Complete| H[Reset State]
        G --> I[End Sync]
        H --> I
    ```
    
    ### Key Characteristics
    - Uses cursor-based pagination with metadata persistence
    - Implements time-remaining checks
    - Gracefully handles the 24-hour limit
    - Maintains sync state across multiple runs
    - Supports automatic resume functionality
    - Ensures data consistency between runs
    
    ### Implementation Notes
    
    This pattern uses metadata to track sync progress and implements time-aware cursor-based pagination. Here's a typical implementation:
    
    ```typescript
    export default async function fetchData(nango: NangoSync): Promise<void> {
        const START_TIME = Date.now();
        const MAX_RUNTIME_MS = 23.5 * 60 * 60 * 1000; // 23.5 hours in milliseconds
        
        // Get or initialize sync metadata
        let metadata = await nango.getMetadata<SyncCursor>();
        
        // Initialize sync window if first run
        if (!metadata?.currentStartTime) {
            await nango.updateMetadata({ 
                currentStartTime: new Date(),
                lastProcessedId: null,
                totalProcessed: 0
            });
            metadata = await nango.getMetadata<SyncCursor>();
        }
        
        let shouldContinue = true;
        
        while (shouldContinue) {
            // Check if we're approaching the 24h limit
            const timeElapsed = Date.now() - START_TIME;
            if (timeElapsed >= MAX_RUNTIME_MS) {
                // Save progress and exit gracefully
                await nango.log('Approaching 24h limit, saving progress and exiting');
                return;
            }
            
            // Fetch and process data batch
            const response = await fetchDataBatch(metadata.lastProcessedId);
            await processAndSaveData(response.data);
            
            // Update progress
            await nango.updateMetadata({
                lastProcessedId: response.lastId,
                totalProcessed: metadata.totalProcessed + response.data.length
            });
            
            // Check if we're done
            if (response.isLastPage) {
                // Reset metadata for fresh start
                await nango.updateMetadata({
                    currentStartTime: null,
                    lastProcessedId: null,
                    totalProcessed: 0
                });
                shouldContinue = false;
            }
        }
    }
    
    async function fetchDataBatch(lastId: string | null): Promise<DataBatchResponse> {
        const config: ProxyConfiguration = {
            endpoint: '/data',
            params: {
                after: lastId,
                limit: 100
            },
            retries: 10
        };
        
        return await nango.get(config);
    }
    ```
    
    Key implementation aspects:
    - Tracks elapsed time to respect the 24-hour limit
    - Maintains detailed progress metadata
    - Implements cursor-based pagination
    - Provides automatic resume capability
    - Ensures data consistency across runs
    - Handles rate limits and data volume constraints
    
    ### Best Practices
    1. Leave buffer time (e.g., stop at 23.5 hours) to ensure clean exit
    2. Save progress frequently
    3. Use efficient batch sizes
    4. Implement proper error handling
    5. Log progress for monitoring
    6. Test resume functionality thoroughly
    
    ### Common Pitfalls
    1. Not accounting for API rate limits in time calculations
    2. Insufficient progress tracking
    3. Not handling edge cases in resume logic
    4. Inefficient batch sizes
    5. Poor error handling
    6. Incomplete metadata management

    Tags

    nangojavascriptintegration

    Comments

    More Rules

    View all
    Web Development

    Next.js 15 + TypeScript Cursor Rules

    Comprehensive .cursorrules file for Next.js 15 App Router projects with TypeScript, enforcing server components by default, proper use of "use client" directive, and App Router conventions.

    C
    Community
    Backend Development

    Python FastAPI Best Practices Rules

    Cursor rules for Python FastAPI projects enforcing async patterns, Pydantic v2 models, dependency injection, and proper error handling.

    C
    Community
    Frontend Development

    React + TypeScript Component Rules

    Rules for consistent React component development with TypeScript interfaces, proper hook patterns, and component composition.

    C
    Community
    AI/ML

    Cursor Agent Mode Configuration

    Rules optimizing Cursor Agent mode behavior including multi-file editing context, session management, and autonomous task completion patterns.

    C
    Cursor Team
    Frontend Development

    Tailwind CSS + shadcn/ui Rules

    Cursor rules for projects using Tailwind CSS with shadcn/ui component library, enforcing consistent utility class usage and component patterns.

    C
    Community
    Backend Development

    Go Backend Service Rules

    Rules for Go backend services enforcing idiomatic Go patterns, proper error handling, and clean architecture conventions.

    C
    Community

    Stay up to date

    Get the latest Cursor prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for Cursor and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.