Skip to content
action-builder-skill logo

Nango Action Builder

action-builder-skill

Use when creating or refactoring Nango integration actions to be thin API wrappers - provides patterns for minimal transformation logic, direct proxy calls, and standardized structure

SKILL.md

Full skill instructions

Nango Action Builder

🚨 REQUIRED: Invoke integration-patterns-skill First

Before using this skill, you MUST invoke the integration-patterns-skill using the Skill tool.

This dependency skill contains critical shared patterns for:

  • Working directory detection (git root ≠ Nango root)
  • Inline schema requirements (NOT from models.ts)
  • ?? null for optional fields
  • Explicit parameter naming (user_id not user)
  • Type safety (inline types, not any)
  • No .default() on Zod schemas
  • index.ts registration requirement
  • Common mistakes table

If you skip invoking it, you WILL miss critical checklist items and make mistakes.

Use Skill tool: integration-patterns-skill

🚫 STOP: nango.yaml Detection

This skill only works with TypeScript-based Nango projects (using createAction()/createSync()).

Before proceeding, check if the project uses the legacy YAML configuration:

ls nango.yaml 2>/​dev/​null && echo "YAML PROJECT DETECTED" || echo "OK - No nango.yaml"

If you see YAML PROJECT DETECTED:

❌ STOP. This skill cannot be used with YAML-based projects.

Tell the user:

"This project uses nango.yaml (legacy configuration). The action-builder-skill only supports TypeScript-based projects using createAction(). Please upgrade your project to the TypeScript format first. See: https://docs.nango.dev/guides/custom-integrations/setup"

Do NOT attempt to:

  • Create actions in a YAML-based project
  • Mix YAML and TypeScript action definitions
  • Use npx nango generate:tests (it doesn't work with YAML projects)

Overview

Actions are thin API wrappers using createAction(). This skill covers action-specific patterns only.

When to Use

  • Adding new API endpoint support
  • Building CRUD operations (create, read, update, delete, list)
  • NOT for: Complex business logic or multi-step workflows (use syncs)

createAction() Structure

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

// Schemas defined inline (see integration-patterns-skill)
const InputSchema = z.object({...});
const OutputSchema = z.object({...});

const action = createAction({
    description: 'Brief single sentence',  // No input params here
    version: '1.0.0',

    endpoint: {
        method: 'POST',           // GET, POST, PATCH, DELETE
        path: '/​resource',        // Static path, NO :params or {params}
        group: 'ResourceGroup'
    },

    input: InputSchema,
    output: OutputSchema,
    scopes: ['required.scope'],

    exec: async (nango, input): Promise<z.infer<typeof OutputSchema>> => {
        const config: ProxyConfiguration = {
            // https://api-docs-url
            endpoint: 'api/​v1/​resource',
            data: {...},          // For POST/​PATCH
            params: {...},        // For GET
            retries: 3            // REQUIRED
        };

        const response = await nango.post(config);  // or .get, .patch, .delete

        return {
            // Transform response to match OutputSchema
            // Use ?? null for optional fields (see integration-patterns-skill)
        };
    }
});

export type NangoActionLocal = Parameters<(typeof action)['exec']>[0];
export default action;

CRUD Methods

OperationMethodConfig Pattern
Createnango.post(config)data: { properties: {...} }
Readnango.get(config)endpoint: 'resource/​${id}', params: {...}
Updatenango.patch(config)endpoint: 'resource/​${id}', data: {...}
Deletenango.delete(config)endpoint: 'resource/​${id}'
Listnango.get(config)params: {...} with pagination

Required in all configs:

  • retries: 3
  • API doc link as comment above endpoint

Optional fields pattern:

data: {
    required_field: input.required,
    ...(input.optional && { optional_field: input.optional })
}

Standard Pagination Interface

All list actions MUST use standardized cursor/next_cursor regardless of provider's native style.

Schema Pattern

const ListInput = z.object({
    cursor: z.string().optional()
        .describe('Pagination cursor from previous response. Omit for first page.')
});

const ListOutput = z.object({
    items: z.array(ItemSchema),
    next_cursor: z.union([z.string(), z.null()])  // null = no more pages
});

Provider Mapping

ProviderNative InputNative OutputMap To
Slackcursorresponse_metadata.next_cursorcursor → next_cursor
Notionstart_cursornext_cursorcursor → next_cursor
HubSpotafterpaging.next.aftercursor → next_cursor
GitHubpageLink headercursor → next_cursor
GooglepageTokennextPageTokencursor → next_cursor

Example

exec: async (nango, input): Promise<z.infer<typeof ListOutput>> => {
    const config: ProxyConfiguration = {
        endpoint: 'api/​items',
        params: {
            ...(input.cursor && { cursor: input.cursor })
        },
        retries: 3
    };

    const response = await nango.get(config);

    return {
        items: response.data.items.map((item: { id: string; name: string }) => ({
            id: item.id,
            name: item.name
        })),
        next_cursor: response.data.next_cursor || null
    };
}

Dryrun Command Syntax

Exact syntax for action dryrun:

npx nango dryrun <action-name> <connection-id> --input '<json>' --integration-id <provider>
                 ↑             ↑                ↑               ↑
                 │             │                │               └── Provider name (slack, hubspot, etc.)
                 │             │                └── JSON string with input params
                 │             └── Connection ID (positional, NOT a flag)
                 └── Action name (positional)

Arguments breakdown:

Position/​FlagExampleDescription
1st positionalget-channel-infoAction name (kebab-case)
2nd positionalaction-builderConnection ID from user
--input'{"channel_id":"C123"}'JSON input (single quotes outside)
--integration-idslackProvider/​integration name

Optional flags:

  • --save-responses - Save API response as mock
  • --validation - Show detailed validation errors
  • --auto-confirm - Skip confirmation prompts

Common Dryrun Mistakes

❌ WRONG - Using --connection-id flag (doesn't exist):

npx nango dryrun get-company hubspot --connection-id abc123 --input '{}'
# Error: Integration "hubspot" does not exist

❌ WRONG - Integration name as second argument:

npx nango dryrun get-company hubspot --input '{}' --integration-id hubspot
# Error: Integration "hubspot" does not exist (hubspot is being read as connection ID)

✅ CORRECT - Connection ID is positional (2nd arg):

npx nango dryrun get-company abc123 --integration-id hubspot --input '{}'
#                            ↑ connection ID here (no flag!)

After Creating an Action

Follow this workflow after creating the action file:

1. Register in index.ts

// Add to index.ts
import './​hubspot/​actions/​get-company-by-domain.js';

2. Run dryrun with --save-responses

npx nango dryrun <action-name> <connection-id> --integration-id <provider> --input '{"param":"value"}' --save-responses

This validates the action works and saves the API response for test mocks.

3. Generate tests

npx nango generate:tests -a <action-name> --integration-id <provider>

# Example:
npx nango generate:tests -a get-company-by-domain --integration-id hubspot

This creates test scaffolding in {provider}/​mocks/​{action-name}/.

4. Run tests

npx nango test -a <action-name> --integration-id <provider>

Complete example workflow:

# After creating hubspot/​actions/​get-company-by-domain.ts

# 1. Register (edit index.ts to add import)

# 2. Dryrun with saved responses
npx nango dryrun get-company-by-domain abc123 --integration-id hubspot --input '{"domain":"nango.dev"}' --save-responses

# 3. Generate tests
npx nango generate:tests -a get-company-by-domain --integration-id hubspot

# 4. Run tests
npx nango test -a get-company-by-domain --integration-id hubspot

Using User-Provided Values

When the user provides test values (connection ID, IDs, etc.), use them:

  1. Connection ID → Use in dryrun command
  2. Test input values (channel ID, user ID, etc.) → Use in:
    • input.json mock file
    • --input flag for dryrun
  3. API reference URL → Fetch for schema details

When API Docs Don't Render

If WebFetch returns incomplete API docs (JavaScript-rendered content):

  1. Use common API patterns - Most REST APIs return similar structures
  2. Ask the user - "Can you provide a sample API response?"
  3. Run dryrun first - Use --save-responses to capture real response, then build schema from it
  4. Check existing actions - Look at similar actions in the codebase for patterns

Mock Directory Structure

{integrationId}/​mocks/
├── meta.json                    # {"connection_id": "my-connection"}
├── <action-name>/
│   ├── input.json               # Test input
│   ├── output.json              # Expected output
│   └── meta.json                # Action-level override (optional)
└── nango/<method>/​proxy/<path>/
    └── <hash>.json              # API response from --save-responses

Action-Specific Checklist

Structure:

  • createAction() with description, version, endpoint, input/​output, scopes
  • Return type is Promise<z.infer<typeof OutputSchema>>
  • export type NangoActionLocal and export default action

Zod Schemas (CRITICAL):

  • NO .default() in any schema - Nango compiler doesn't support it. Handle defaults in exec function instead.

ProxyConfiguration:

  • retries: 3 configured
  • API doc link comment above endpoint
  • Uses input directly (no zodValidateInput)

Pagination (list actions only):

  • Input uses cursor: z.string().optional()
  • Output uses next_cursor: z.union([z.string(), z.null()])

See integration-patterns-skill for: schema, naming, typing, path, and index.ts registration checklist items.

Action-Specific Mistakes

MistakeWhy It FailsFix
Missing retries: 3Flaky network calls failAdd to ProxyConfiguration
Wrong return typeType mismatch errorsUse Promise<z.infer<typeof OutputSchema>>
Using zodValidateInputReturns undefined, already validatedUse input directly
Provider-specific paginationInconsistent APIUse cursor/next_cursor standard
Importing mapper functionsNot self-containedInline transformations in exec

For schema, naming, typing, registration mistakes → invoke integration-patterns-skill

More skills from diegosouzapw

accessibility logo
diegosouzapw/awesome-omni-skill

accessibility

Accessibility guidelines for VS Code features — covers accessibility help dialogs, accessible views, verbosity settings, accessibility signals, ARIA alerts/status announcements, keyboard navigation, and ARIA labels/roles. Applies to both new interactive UI surfaces and updates to existing feature...

62 1
View
mermaid-diagrams logo
diegosouzapw/awesome-omni-skill

mermaid-diagrams

Mermaid diagram creation for flowcharts, sequences, ERDs, and more. Generate diagrams from text in markdown files. Use for documentation, architecture diagrams, and visual representations. Triggers on mermaid, flowchart, sequence diagram, ERD, entity relationship, gantt chart, pie chart, class di...

62 1
View
tauri2-react-rust logo
diegosouzapw/awesome-omni-skill

tauri2-react-rust

Guides development of cross-platform desktop apps with Tauri 2, TypeScript, React, and Rust. Use when building Tauri apps, implementing IPC, designing Rust backend or TypeScript/React frontend, when researching or cloning a website (open site, snapshot elements), when verifying local dev or built...

62 1
View
fp-ts-errors logo
diegosouzapw/awesome-omni-skill

fp-ts-errors

Handle errors as values using fp-ts Either and TaskEither for cleaner, more predictable TypeScript code. Use when implementing error handling patterns with fp-ts.

62 0
View
ring:pre-dev-research logo
diegosouzapw/awesome-omni-skill

ring:pre-dev-research

Gate 0 research phase for pre-dev workflow. Dispatches 3 parallel research agents to gather codebase patterns, external best practices, and framework documentation BEFORE creating PRD/TRD. Outputs research.md with file:line references.

62 0
View
create-assistant logo
diegosouzapw/awesome-omni-skill

create-assistant

Create and configure Vapi voice AI assistants with models, voices, transcribers, tools, hooks, and advanced settings. Use when building voice agents, phone bots, customer support assistants, or any conversational AI that handles phone or web calls.

62 0
View
codex-headless logo
diegosouzapw/awesome-omni-skill

codex-headless

Delegiere Aufgaben an OpenAI Codex CLI im Headless-Mode. Nutzt ChatGPT Subscription (KEIN API Key). Nur Workspace-Zugriff, KEIN System-Zugriff. Auto-Accept mit --full-auto. Web-Recherche mit --search.

62 0
View
social-media-scheduler logo
diegosouzapw/awesome-omni-skill

social-media-scheduler

Generate a full week of social media content for any topic. Outputs platform-optimized posts for Twitter/X, LinkedIn, and Instagram with hashtags and posting times.

62 0
View
Data Privacy Compliance logo
diegosouzapw/awesome-omni-skill

Data Privacy Compliance

Data privacy and regulatory compliance specialist for GDPR, CCPA, HIPAA, and international data protection laws. Use when implementing privacy controls, conducting data protection impact assessments, ensuring regulatory compliance, or managing data subject rights. Expert in consent management, da...

62 0
View
triple-review logo
diegosouzapw/awesome-omni-skill

triple-review

Reviews a task using the TRIPLE protocol, as the review agent.

62 0
View
Tech Stack & Architecture Decision logo
diegosouzapw/awesome-omni-skill

Tech Stack & Architecture Decision

Define and document the technology stack and architecture decisions for a project. Use when the user needs to choose a tech stack, make architecture decisions, define infrastructure choices, or document technology selections. Triggers on requests like "define the tech stack", "choose technologies...

62 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View