mcpField
B
building-nango-functions

building-nango-functions

github.com/nangohqDevelopment3232 installsUpdated 10w ago
Repository

About building-nango-functions

building-nango-functions is a skill for designing and implementing Nango actions and syncs. It sets out implementation constraints, input requirements, and checkpoint and deletion strategies while leaving validation and deployment mechanics to workflow-specific skills.

Key features

  1. 01Design Nango actions with `createAction()`
  2. 02Design Nango syncs with `createSync()`
  3. 03Plan checkpoints and deletion handling
  4. 04Apply schema and runtime constraints
  5. 05Route local or remote workflows to specialized skills

Use cases

  1. 01Building a one-time Nango action
  2. 02Implementing a scheduled or webhook-driven sync
  3. 03Choosing a checkpoint and deletion strategy
  4. 04Preparing a function for workflow-specific validation

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/nangohq/skills --skill building-nango-functions

After installing, type/building-nango-functionsin Claude Code to confirm it loaded.

Bundled filesreferences/actions.mdreferences/syncs.md

Details

From the project README

Build Nango Functions

Build Nango action and sync implementations without choosing the execution workflow.

This skill covers the function design contract: schemas, provider calls, action outputs, sync models, checkpoints, deletion handling, metadata, retries, and runtime constraints. It intentionally does not cover local CLI validation/deploy or remote API compile/dryrun/deploy.

If the task becomes clearly local/CLI-based, use building-nango-functions-locally instead. If it becomes clearly remote/API-based, use building-nango-functions-remotely instead.

Implementation Scope

  • Build or modify a Nango function implementation
  • Build an action in Nango with createAction()
  • Build a sync in Nango with createSync()
  • Use the active workflow skill for compile, dryrun, test, and deploy mechanics

Sync Strategy Gate (required before writing code)

If the task is a sync, read references/syncs.md before writing code and state one of these paths first:

  • Checkpoint plan:
    • change source (updated_at, modified_since, changed-records endpoint, cursor, page token, offset/page, since_id, or webhook)
    • checkpoint schema
    • how the checkpoint changes the provider request or resume state
    • whether the request still walks the full dataset or returns changed rows only
    • delete strategy
  • Full refresh blocker:
    • exact provider limitation from the docs or sample payloads
    • why checkpoints cannot work here

Invalid sync implementations:

  • full refresh because it is simpler
  • saveCheckpoint() without getCheckpoint()
  • reading or saving a checkpoint without using it in request params or pagination state
  • using syncType: 'incremental' or nango.lastSyncDate in a new sync
  • a full refresh with no checkpoint schema, or one that is never saved after each page — the run restarts from page 1 whenever it exceeds the execution window
  • saveCheckpoint() guarded so it only runs when more pages remain (e.g. if (nextCursor) { await nango.saveCheckpoint(...) }). Every successful page, including the last, must call saveCheckpoint(); otherwise a one-page sync reaches clearCheckpoint() with no checkpoint row and fails with checkpoint_conflict.
  • calling trackDeletesEnd() before clearCheckpoint(), or without a preceding clearCheckpoint() at all
  • using trackDeletesStart() / trackDeletesEnd() with a changed-only checkpoint (modified_after, updated_after, changed-records endpoint). Those requests omit unchanged rows, so trackDeletesEnd() will falsely delete them.
  • using trackDeletesStart() / trackDeletesEnd() in an incremental sync that already has explicit deleted-record events

Choose the Path

Action:

  • One-time request, user-triggered, built with createAction()
  • Read references/actions.md before writing code

Sync:

  • Scheduled or webhook-driven cache updates built with createSync()
  • Complete the Sync Strategy Gate first
  • Read references/syncs.md before writing code

Required Inputs (Ask User if Missing)

Always:

  • Integration ID (provider name)
  • Script/function name (kebab-case)
  • API reference URL or sample response
  • Connection ID if the active workflow will validate or dryrun the function

Action-specific:

  • Use case summary
  • Input parameters
  • Output fields
  • Metadata JSON if required
  • Test input JSON if the active workflow will validate or dryrun the action (use {} for no-input actions)

Sync-specific:

  • Model name (singular, PascalCase)
  • Frequency (every hour, every 5 minutes, etc.)
  • Checkpoint schema (timestamp, cursor, page token, offset/page, since_id, or composite)
  • How the checkpoint changes the provider request or resume state
  • Delete strategy (deleted-record endpoint/webhook, or why full refresh is required)
  • If proposing a full refresh, the exact provider limitation that blocks checkpoints from the docs/sample response
  • Metadata JSON if required (team_id, workspace_id)

If any required external values are missing, ask a targeted question after checking the repo and provider docs. For syncs, choose a checkpoint plus deletion strategy whenever the provider supports one. If you cannot find a viable checkpoint strategy, state exactly why before writing a full refresh.

Non-Negotiable Rules

Shared platform constraints
  • Nango functions use createAction() / createSync().
  • You cannot add arbitrary packages. Use relative imports only when the chosen workflow supports them; built-ins include zod, crypto/node:crypto, and url/node:url.
  • Use the Nango HTTP API for connection lookup, credentials, and proxy calls outside function code. Do not invent CLI token or connection commands.
  • Add an API doc link comment above each provider call.
  • Action outputs cannot exceed 2MB.
  • File uploads and downloads cannot be implemented as actions (sandboxed runtime: no fs, no axios, 2 MB output limit). Use a proxy script in {integration}/proxy/ with @nangohq/node instead — see references/actions.md.
  • HTTP retries default to 0; set retries deliberately. Treat 3 as the normal maximum; for sync provider calls, values above 3 are effectively forbidden unless docs prove they are safe and necessary. Avoid retries for non-idempotent writes unless the API supports idempotency.
  • Do not set deprecated function definition routing fields: action endpoint and sync endpoints. Trigger actions by action name through the SDK/API, and consume sync records through the records API.
Sync rules
  • Sync records need a stable string id.
  • New syncs should define a checkpoint schema, call nango.getCheckpoint() first, and nango.saveCheckpoint() after each page or batch.
  • A checkpoint is valid only if it changes the request or resume state (since, updated_after, cursor, page_token, offset, page, since_id, etc.). Saving one without using it is not incremental sync.
  • New syncs must not use syncType: 'incremental' or nango.lastSyncDate.
  • Default to nango.paginate(...) + nango.batchSave(...). Avoid manual while (true) loops when cursor, link, or offset pagination fits.
  • Prefer batchDelete() when the provider returns deletions, tombstones, or delete webhooks.
  • Use full refresh only if the provider cannot return changes, deletions, or resume state, or if the dataset is tiny.
  • For full refresh, cite the exact provider limitation from docs or payloads. "It is easier" is not enough.
  • Full refresh syncs still need a checkpoint schema (page/cursor/offset) covering pagination progress, not just incremental syncs. Nango syncs run inside a time-limited execution window; a full refresh with no checkpoint restarts from page 1 on every run that exceeds the window, wasting compute re-fetching the same early pages and never reaching the rest.
  • deleteRecordsFromPreviousExecutions() is deprecated. For full refresh, call trackDeletesStart() on every execution (safe/idempotent — it will not overwrite the start of an already-open window), then saveCheckpoint() after each page, clearCheckpoint() after the last page, and trackDeletesEnd() only after that clearCheckpoint().
  • In a full refresh, call saveCheckpoint() after every successful page, including the last, before calling clearCheckpoint(). Never guard the save with "more pages remain." If a distinct execution path creates no checkpoint at all (for example, it processes no pages), do not call clearCheckpoint() on that path; it throws checkpoint_conflict at runtime. This exception is not a substitute for saving the terminal page.
  • Never combine trackDeletesStart() / trackDeletesEnd() with changed-only checkpoints (modified_after, updated_after, changed-records endpoints, etc.). They omit unchanged rows, so trackDeletesEnd() would delete them.
  • Checkpointed full refreshes are still full refreshes. Call trackDeletesEnd() only in the run that finishes and clears the checkpoint.
  • If a sync requires metadata (e.g. team_id, workspace_id, guild_id), set autoStart: false. The sync cannot run until the caller has set the metadata, so starting it automatically would fail.
Conventions
  • Match field casing to the external API. Passthrough fields keep provider casing; non-passthrough fields should use the majority casing of that API.
  • Prefer explicit field names.
  • Add .describe() examples for IDs, timestamps, enums, and URLs.
  • Avoid any; use inline mapping types.
  • List actions should expose cursor plus a next-cursor field in the majority casing of that API (next_cursor, nextCursor, etc.).
  • Use nango.zodValidateInput() only when you need custom validation or logging; otherwise rely on schemas plus the chosen validation workflow.
Schema Semantics
  • Default non-required inputs to .optional().
  • Use .nullable() only when null has meaning, usually clear-on-update; add .optional() when callers may omit the field too.
  • Raw provider schemas should match the provider: .optional() for omitted fields, .nullable() for explicit null, .nullish() only when the provider truly does both.
  • Final action outputs and normalized sync models should prefer .optional() and normalize upstream null to omission unless null matters.
  • Default generated schemas to .optional() for non-required inputs and normalized outputs; widen only when the upstream contract justifies it.
  • Prefer .nullable() over z.union([z.null(), T]) or z.union([T, z.null()]).
  • Return null only when the output schema allows it.
  • z.object() strips unknown keys by default. For provider pass-through use z.object({}).passthrough(), z.record(z.unknown()), or z.unknown() with minimal refinements.
Field Naming and Casing Rules
  • Use explicit suffixes in the API's majority casing: IDs (user_id, userId), names (channel_name, channelName), emails (user_email, userEmail), URLs (callback_url, callbackUrl), and timestamps (created_at, createdAt).

Mapping example (API expects a different parameter name):

const InputSchema = z.object({
    userId: z.string()
});

const config: ProxyConfiguration = {
    endpoint: 'users.info',
    params: {
        user: input.userId
    },
    retries: 3
};

If the API is snake_case, use user_id instead. The goal is API consistency.

References

  • Action patterns, CRUD examples, metadata usage, and ActionError examples: references/actions.md
  • Sync patterns, concrete checkpoint examples, delete strategies, and full refresh fallback: references/syncs.md

Useful Nango docs (quick links)

When API Docs Do Not Render

If web fetching returns incomplete docs (JS-rendered):

  • Ask the user for a sample response
  • Use existing Nango actions or syncs in the workspace as a pattern when they exist
  • Use the skill-specific validation or dryrun workflow until it passes

Comments

Markdown supported