Skip to main content

building-nango-functions

Builds Nango Function implementation patterns for createAction() and createSync() without choosing a local CLI or remote API workflow. Use only when the user asks to create or update a Nango action or sync and it is unclear whether the work should happen in a checked-out project via CLI or through Nango remote APIs. Do not load when building-nango-functions-locally or building-nango-functions-remotely applies; those skills overlap with this content and add workflow-specific validation and deploy details.

Source facts

Repository
NangoHQ/skills
Last source activity
August 20, 2026 at 14:36
Detected SKILL.md language
English
Stars
2
Forks
3

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
3 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
building-nango-functions
description
Builds Nango Function implementation patterns for createAction() and createSync() without choosing a local CLI or remote API workflow. Use only when the user asks to create or update a Nango action or sync and it is unclear whether the work should happen in a checked-out project via CLI or through Nango remote APIs. Do not load when building-nango-functions-locally or building-nango-functions-remotely applies; those skills overlap with this content and add workflow-specific validation and deploy details.
# 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): ```typescript 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) - Functions runtime SDK reference: https://nango.dev/docs/reference/functions - Implement an action: https://nango.dev/docs/implementation-guides/use-cases/actions/implement-an-action - Implement a sync: https://nango.dev/docs/implementation-guides/use-cases/syncs/implement-a-sync - Checkpoints: https://nango.dev/docs/implementation-guides/use-cases/syncs/checkpoints - Deletion detection (full vs incremental): https://nango.dev/docs/implementation-guides/use-cases/syncs/deletion-detection - Testing integrations (dryrun, `--save`, Vitest): https://nango.dev/docs/implementation-guides/platform/functions/testing - Nango HTTP API reference: https://nango.dev/docs/reference/api ## 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
View on GitHub