skills/building-nango-functions-remotely/SKILL.md
Builds Nango Functions without a checked-out Nango project by using the Nango /functions compile, dryrun, dryrun status, and deployment APIs with NANGO_SERVER_URL and NANGO_SECRET_KEY. Use when creating, updating, validating, testing, or deploying Nango actions or syncs remotely via API or single-file payloads. This content overlaps with building-nango-functions but adds remote API workflow details, so load this instead of building-nango-functions whenever remote, API, /functions/compile, /functions/dryruns, /functions/deployments, or no local project workflow is indicated.
npx skillsauth add nangohq/skills building-nango-functions-remotelyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build Nango actions and syncs without a checked-out Nango project by sending a single-file TypeScript function to Nango's /functions/compile, /functions/dryruns, and /functions/deployments APIs.
createAction()createSync()If the task is a sync, read references/syncs.md before writing code and state one of these paths first:
updated_at, modified_since, changed-records endpoint, cursor, page token, offset/page, since_id, or webhook)Invalid sync implementations:
saveCheckpoint() without getCheckpoint()syncType: 'incremental' or nango.lastSyncDate in a new synctrackDeletesStart() / trackDeletesEnd() with a changed-only checkpoint (modified_after, updated_after, changed-records endpoint). Those requests omit unchanged rows, so trackDeletesEnd() will falsely delete them.trackDeletesStart() / trackDeletesEnd() in an incremental sync that already has explicit deleted-record eventsAction:
createAction()references/actions.md before writing codeSync:
createSync()references/syncs.md before writing codeAlways:
Action-specific:
{} for no-input actions)Sync-specific:
since_id, or composite)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.
createAction() / createSync().zod, crypto/node:crypto, and url/node:url.fs, no axios, 2 MB output limit). Use a proxy script in {integration}/proxy/ with @nangohq/node instead — see references/actions.md.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.id.checkpoint schema, call nango.getCheckpoint() first, and nango.saveCheckpoint() after each page or batch.since, updated_after, cursor, page_token, offset, page, since_id, etc.). Saving one without using it is not incremental sync.syncType: 'incremental' or nango.lastSyncDate.nango.paginate(...) + nango.batchSave(...). Avoid manual while (true) loops when cursor, link, or offset pagination fits.batchDelete() when the provider returns deletions, tombstones, or delete webhooks.deleteRecordsFromPreviousExecutions() is deprecated. For full refresh, call trackDeletesStart() before fetch/save and trackDeletesEnd() only after a successful full fetch/save.trackDeletesStart() / trackDeletesEnd() with changed-only checkpoints (modified_after, updated_after, changed-records endpoints, etc.). They omit unchanged rows, so trackDeletesEnd() would delete them.trackDeletesEnd() only in the run that finishes the full window.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..describe() examples for IDs, timestamps, enums, and URLs.any; use inline mapping types.:id / {id} in the exposed endpoint); pass IDs in input or params.cursor plus a next-cursor field in the majority casing of that API (next_cursor, nextCursor, etc.).nango.zodValidateInput() only when you need custom validation or logging; otherwise rely on schemas plus the chosen validation workflow..optional()..nullable() only when null has meaning, usually clear-on-update; add .optional() when callers may omit the field too..optional() for omitted fields, .nullable() for explicit null, .nullish() only when the provider truly does both..optional() and normalize upstream null to omission unless null matters..optional() for non-required inputs and normalized outputs; widen only when the upstream contract justifies it..nullable() over z.union([z.null(), T]) or z.union([T, z.null()]).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.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/actions.mdreferences/syncs.md--save, Vitest): https://nango.dev/docs/implementation-guides/platform/functions/testingIf web fetching returns incomplete docs (JS-rendered):
NANGO_SERVER_URL in this order: environment variable, .env file, then fallback to https://api.nango.dev.NANGO_SECRET_KEY before calling function endpoints.environment:functions:compile for compile, environment:functions:dryrun for dryrun and polling, and environment:deploy for deployment.references/actions.md or references/syncs.md.NANGO_SECRET_KEY for the target environment and any metadata needed for dryrun.NANGO_SERVER_URL in the environment, then .env, then https://api.nango.dev.createAction() or createSync().POST {host}/functions/compile until compilation passes.POST {host}/functions/dryruns using the target integration, connection, function type, code, and any input, metadata, or checkpoint needed.GET {host}/functions/dryruns/{id} until the dryrun reaches success or failed.POST {host}/functions/deployments only when requested.GET {host}/functions/deployments/{id} until the deployment reaches success or failed.Read references/api.md before making remote calls.
Required sequence:
POST /functions/compile.POST /functions/dryruns.GET /functions/dryruns/{id} until terminal.POST /functions/deployments only when requested.GET /functions/deployments/{id} until terminal.Rules:
NANGO_SERVER_URL.Authorization: Bearer <NANGO_SECRET_KEY> and Content-Type: application/json.environment:functions:compile for compile, environment:functions:dryrun for dryrun create/status, and environment:deploy for deployment create/status.{ "code": "..." }.integration_id, function_type, code, and connection_id.type: "function", integration_id, function_name, function_type, and code. Add version or allow_destructive only when explicitly needed.input and metadata only when needed.metadata and checkpoint when needed to simulate a resumed run. Do not introduce last_sync_date for a new sync design.POST /functions/dryruns returns an id; poll GET /functions/dryruns/{id} for status, output, result, or error. Do not call /functions/dryruns/{id}/result; it is sandbox-internal.POST /functions/deployments returns an id; poll GET /functions/deployments/{id} for status, deployed, deployed_functions, output, or error. Do not call /functions/deployments/{id}/result; it is sandbox-internal.waiting and running as nonterminal; stop polling only on success or failed.--validate or --save; it compiles before running and returns the execution result through the status endpoint, but it does not record local mocks.Action:
references/actions.md was used for the action patterncreateAction() includes endpoint, input, output, and scopes when requirednango.ActionError is used for expected failuresNANGO_SERVER_URL, .env, or https://api.nango.devPOST /functions/compilePOST /functions/dryrunssuccess through GET /functions/dryruns/{id} with the expected action outputPOST /functions/deployments when requestedsuccess through GET /functions/deployments/{id} when requestedSync:
references/syncs.md was used for the sync pattern.optional() unless null mattersnango.getCheckpoint() is read at the start and nango.saveCheckpoint() runs after each page or batchsince, updated_after, cursor, page_token, offset, page, since_id, etc.)modified_after, updated_after, changed-records endpoint) do not use trackDeletesStart() / trackDeletesEnd()retries: 3; no sync retry value exceeds 3 without a documented exceptionNANGO_SERVER_URL, .env, or https://api.nango.devPOST /functions/compilePOST /functions/dryrunssuccess through GET /functions/dryruns/{id} and returns the expected change setPOST /functions/deployments when requestedsuccess through GET /functions/deployments/{id} when requesteddevelopment
Copy-pasteable Nango quickstart prompt for agents. Guides brand-new Nango customers from signup through API key setup, integration setup, connection authorization, and action/sync next steps. Use for first Nango integration walkthroughs.
tools
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.
development
Migrates existing Nango TypeScript createSync implementations from nango.lastSyncDate, legacy incremental syncType, and non-resumable full refreshes to checkpoint-based syncs. Use when updating customer Nango sync code to define checkpoint schemas, call getCheckpoint/saveCheckpoint/clearCheckpoint after every batchSave (including inside paginate loops), test dryruns with --checkpoint, and fix deletion handling for checkpointed incremental or full syncs.
tools
Universal gateway for any third-party API or SaaS (Google Calendar, Gmail, Slack, Notion, Linear, HubSpot, etc.). TRIGGER on any request to read or modify data in an external product, even when no matching MCP tool is loaded.