name: building-nango-functions-remotely description: 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.
Build Nango Functions Remotely
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.
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
- change source (
- 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()withoutgetCheckpoint()- reading or saving a checkpoint without using it in request params or pagination state
- using
syncType: 'incremental'ornango.lastSyncDatein a new sync - using
trackDeletesStart()/trackDeletesEnd()with a changed-only checkpoint (modified_after,updated_after, changed-records endpoint). Those requests omit unchanged rows, sotrackDeletesEnd()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.mdbefore writing code
Sync:
- Scheduled or webhook-driven cache updates built with
createSync() - Complete the Sync Strategy Gate first
- Read
references/syncs.mdbefore 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, andurl/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, noaxios, 2 MB output limit). Use a proxy script in{integration}/proxy/with@nangohq/nodeinstead — seereferences/actions.md. - HTTP retries default to
0; setretriesdeliberately. Treat3as the normal maximum; for sync provider calls, values above3are effectively forbidden unless docs prove they are safe and necessary. Avoid retries for non-idempotent writes unless the API supports idempotency.
Sync rules
- Sync records need a stable string
id. - New syncs should define a
checkpointschema, callnango.getCheckpoint()first, andnango.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'ornango.lastSyncDate. - Default to
nango.paginate(...)+nango.batchSave(...). Avoid manualwhile (true)loops whencursor,link, oroffsetpagination 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.
deleteRecordsFromPreviousExecutions()is deprecated. For full refresh, calltrackDeletesStart()before fetch/save andtrackDeletesEnd()only after a successful full fetch/save.- Never combine
trackDeletesStart()/trackDeletesEnd()with changed-only checkpoints (modified_after,updated_after, changed-records endpoints, etc.). They omit unchanged rows, sotrackDeletesEnd()would delete them. - Checkpointed full refreshes are still full refreshes. Call
trackDeletesEnd()only in the run that finishes the full window. - If a sync requires metadata (e.g.
team_id,workspace_id,guild_id), setautoStart: 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. - Prefer static Nango endpoint paths (avoid
:id/{id}in the exposed endpoint); pass IDs in input or params. - List actions should expose
cursorplus 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 whennullhas 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 explicitnull,.nullish()only when the provider truly does both. - Final action outputs and normalized sync models should prefer
.optional()and normalize upstreamnullto omission unlessnullmatters. - Default generated schemas to
.optional()for non-required inputs and normalized outputs; widen only when the upstream contract justifies it. - Prefer
.nullable()overz.union([z.null(), T])orz.union([T, z.null()]). - Return
nullonly when the output schema allows it. z.object()strips unknown keys by default. For provider pass-through usez.object({}).passthrough(),z.record(z.unknown()), orz.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)
- 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
Preconditions (Do Before Writing Code)
- No checked-out Nango project is required.
- Resolve
NANGO_SERVER_URLin this order: environment variable,.envfile, then fallback tohttps://api.nango.dev. - Resolve
NANGO_SECRET_KEYbefore calling function endpoints. - Use the environment bound to that secret key.
- Confirm the key has the needed scope:
environment:functions:compilefor compile,environment:functions:dryrunfor dryrun and polling, andenvironment:deployfor deployment. - Keep the function self-contained in one TypeScript file unless you have direct evidence that the remote endpoint accepts multi-file payloads.
- Do NOT create or modify any files in the current project/directory. If you need to create files, use a temp folder
Workflow (required)
- Decide whether this is an action or a sync.
- Read the matching reference file:
references/actions.mdorreferences/syncs.md. - For syncs, inspect provider docs or payloads for checkpoints and deletes, decide whether the endpoint returns full data or changed rows, and complete the Sync Strategy Gate.
- Gather required inputs and external values, including the
NANGO_SECRET_KEYfor the target environment and any metadata needed for dryrun. - Resolve the host from
NANGO_SERVER_URLin the environment, then.env, thenhttps://api.nango.dev. - Write or update the function as one self-contained TypeScript file using
createAction()orcreateSync(). - Compile with
POST {host}/functions/compileuntil compilation passes. - Start a dryrun with
POST {host}/functions/dryrunsusing the target integration, connection, function type, code, and anyinput,metadata, orcheckpointneeded. - Poll
GET {host}/functions/dryruns/{id}until the dryrun reachessuccessorfailed. - If compile or dryrun cannot pass, stop and report the missing external state, inputs, or API contract mismatch.
- Start deployment with
POST {host}/functions/deploymentsonly when requested. - Poll
GET {host}/functions/deployments/{id}until the deployment reachessuccessorfailed.
Remote API Workflow (required)
Read references/api.md before making remote calls.
Required sequence:
- Compile first with
POST /functions/compile. - Start dryrun second with
POST /functions/dryruns. - Poll dryrun status with
GET /functions/dryruns/{id}until terminal. - Start deployment last with
POST /functions/deploymentsonly when requested. - Poll deployment status with
GET /functions/deployments/{id}until terminal.
Rules:
- These endpoints are relative. Always resolve them against the chosen
NANGO_SERVER_URL. - Send
Authorization: Bearer <NANGO_SECRET_KEY>andContent-Type: application/json. - Required API key scopes are
environment:functions:compilefor compile,environment:functions:dryrunfor dryrun create/status, andenvironment:deployfor deployment create/status. - Do not send query params unless the API docs or an existing caller prove they are supported.
- Use the server's validation errors to correct payloads. Do not invent undocumented fields when the API rejects a request.
- Compile sends only
{ "code": "..." }. - Dryrun sends
integration_id,function_type,code, andconnection_id. - Deploy sends
type: "function",integration_id,function_name,function_type, andcode. Addversionorallow_destructiveonly when explicitly needed. - For actions, dryrun should include
inputandmetadataonly when needed. - For syncs, dryrun should include
metadataandcheckpointwhen needed to simulate a resumed run. Do not introducelast_sync_datefor a new sync design. - Dryrun is asynchronous.
POST /functions/dryrunsreturns anid; pollGET /functions/dryruns/{id}forstatus,output,result, orerror. Do not call/functions/dryruns/{id}/result; it is sandbox-internal. - Deployment is asynchronous.
POST /functions/deploymentsreturns anid; pollGET /functions/deployments/{id}forstatus,deployed,deployed_functions,output, orerror. Do not call/functions/deployments/{id}/result; it is sandbox-internal. - Treat
waitingandrunningas nonterminal; stop polling only onsuccessorfailed. - Remote dryrun does not expose CLI
--validateor--save; it compiles before running and returns the execution result through the status endpoint, but it does not record local mocks.
Final Checklists
Action:
-
references/actions.mdwas used for the action pattern - Schemas and types are clear, and the function stays self-contained in one file
-
createAction()includes endpoint, input, output, and scopes when required - Fields use passthrough casing or the API's majority casing
- Provider call includes an API doc link comment and intentional retries
-
nango.ActionErroris used for expected failures - Host was resolved from
NANGO_SERVER_URL,.env, orhttps://api.nango.dev - Compile succeeds with
POST /functions/compile - Dryrun was started with
POST /functions/dryruns - Dryrun status reached
successthroughGET /functions/dryruns/{id}with the expected action output - Deployment was started with
POST /functions/deploymentswhen requested - Deployment status reached
successthroughGET /functions/deployments/{id}when requested
Sync:
-
references/syncs.mdwas used for the sync pattern - Models map is defined, ids are stable strings, and normalized models prefer
.optional()unlessnullmatters - Incremental was chosen first, with a checkpoint schema unless full refresh is explicitly justified from docs or payloads
-
nango.getCheckpoint()is read at the start andnango.saveCheckpoint()runs after each page or batch - Checkpoint data changes the provider request or resume state (
since,updated_after,cursor,page_token,offset,page,since_id, etc.) - Changed-only checkpoint syncs (
modified_after,updated_after, changed-records endpoint) do not usetrackDeletesStart()/trackDeletesEnd() - If checkpoints were not used, the response explains exactly why no viable checkpoint strategy exists
- Provider API calls use
retries: 3; no sync retry value exceeds3without a documented exception - The function stays self-contained in one file unless the remote API proves multi-file support
- Host was resolved from
NANGO_SERVER_URL,.env, orhttps://api.nango.dev - Compile succeeds with
POST /functions/compile - Dryrun was started with
POST /functions/dryruns - Dryrun status reached
successthroughGET /functions/dryruns/{id}and returns the expected change set - Deployment was started with
POST /functions/deploymentswhen requested - Deployment status reached
successthroughGET /functions/deployments/{id}when requested