skills/migrating-to-checkpoints/SKILL.md
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.
npx skillsauth add nangohq/skills migrating-to-checkpointsInstall 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.
Update existing createSync() code to use checkpoints. Preserve provider behavior first; only change the progress/resume mechanism and the deletion handling needed to make checkpointing correct.
nango.yaml still defines the sync, suggest migrating to Zero YAML as a next step before or alongside the checkpoint migration. Mention the migrating-to-zero-yaml skill, suggest npx skills add https://github.com/NangoHQ/skills --skill migrating-to-zero-yaml if it is not installed, and link the docs: https://nango.dev/docs/implementation-guides/platform/migrations/migrate-to-zero-yaml.saveCheckpoint() immediately after every successful batchSave() (and after batchUpdate() / batchDelete() when those advance progress). This is required inside pagination loops (for await over nango.paginate, manual cursor/offset loops, nested fetches)—not only once at the end of exec. Saving only after a helper returns or only at the end of exec leaves no durable progress if the run fails mid-pagination.Date objects, arrays, or nested objects.trackDeletesStart() / trackDeletesEnd(). Those endpoints omit unchanged rows, so Nango would mark unchanged records as deleted.Search for legacy and partial checkpoint patterns:
rg -n "lastSyncDate|syncType:\s*['\"]incremental|deleteRecordsFromPreviousExecutions|trackDeletes:|track_deletes|getCheckpoint|saveCheckpoint|clearCheckpoint" .
For each sync, identify:
updated_at, modified_since, changed-records endpoint cursor, page token, offset/page, since_id, etc.batchSave, batchUpdate, and batchDelete.z.object({ updated_after: z.string() }).z.object({ cursor: z.string() }).z.object({ page: z.number() }) or z.object({ offset: z.number() }).z.object({ updated_after: z.string(), page_token: z.string().optional() }).clearCheckpoint() only after the full dataset was fetched and saved successfully.If the provider cannot filter changes and cannot resume pagination, do not force checkpoints. State the provider limitation and keep or convert to an explicitly justified full refresh.
createSync():const CheckpointSchema = z.object({
updated_after: z.string(),
});
export default createSync({
description: "Sync contacts",
frequency: "every hour",
checkpoint: CheckpointSchema,
models: { Contact },
exec: async (nango) => {
// ...
},
});
Remove syncType: 'incremental' from incremental migrations. The checkpoint field replaces that legacy signal.
Replace nango.lastSyncDate reads with await nango.getCheckpoint() and use the checkpoint in the provider request:
const checkpoint = await nango.getCheckpoint();
const response = await nango.get({
endpoint: "/contacts",
params: {
...(checkpoint?.updated_after && {
updated_after: checkpoint.updated_after,
}),
},
retries: 3,
});
batchSave(), call saveCheckpoint() in the same loop iteration—including inside nango.paginate and any extracted pagination helper. Pair them; do not defer checkpoint writes to the end of exec.const contacts = response.data.items.map(mapContact);
await nango.batchSave(contacts, "Contact");
if (contacts.length > 0) {
const lastContact = contacts[contacts.length - 1]!;
await nango.saveCheckpoint({ updated_after: lastContact.updated_at });
}
Paginated sync:
for await (const page of nango.paginate<Item>(config)) {
const records = page.map(mapRecord);
await nango.batchSave(records, "Contact");
await nango.saveCheckpoint({ updated_after: latestTimestamp(records) });
}
Invalid: await getAndSaveUsers(nango) that only calls batchSave inside the loop, then saveCheckpoint once in exec after the helper returns.
For timestamp checkpoints, prefer the provider record's sorted last-modified value. If records can share the same timestamp or the API cannot sort stably, use a composite checkpoint with a provider cursor/page token or a tie-breaker field such as last_id.
Do not accumulate all records in memory just to save one final checkpoint. Process, batchSave, and saveCheckpoint page by page.
Use this only when the API cannot return changed rows but can resume pagination. The checkpoint is for failure recovery, not incremental filtering.
batchSave(), call saveCheckpoint() with the next cursor/page (same loop body—never only at the end of exec).clearCheckpoint() only after the last page is saved.batchDelete() for those IDs using the same checkpoint window.trackDeletesStart('Model') before fetching/saving and trackDeletesEnd('Model') only after the full dataset is saved and the checkpoint is cleared.trackDeletesStart() can run at the start of each execution; trackDeletesEnd() belongs only in the execution that completes the full refresh.deleteRecordsFromPreviousExecutions(). It is incompatible with checkpointed syncs.Run the local validation loop from the Nango project root:
nango compile
nango dryrun <sync-name> <connection-id> --validate -e dev --no-interactive --auto-confirm
nango dryrun <sync-name> <connection-id> --validate -e dev --no-interactive --auto-confirm --checkpoint '{"updated_after":"2024-06-01T00:00:00Z"}'
Use --metadata when the sync needs metadata, tailor the --checkpoint payload to the schema, and run the repo's existing test suite if one exists.
nango.lastSyncDate references in migrated syncssyncType: 'incremental' left for checkpointed incremental syncscheckpoint schema exists and uses only flat primitive valuesgetCheckpoint() is called before provider requestsbatchSave()/batchUpdate()/batchDelete() that advances progress is immediately followed by saveCheckpoint() in the same loop (including inside nango.paginate and pagination helpers—not only once after exec returns)clearCheckpoint() only after successful completionbatchDelete() for explicit provider deletes, trackDeletesStart/End only for full refresh--checkpointdevelopment
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.
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.
development
Manually migrates legacy `nango.yaml` Nango projects to Zero YAML TypeScript by generating `models.ts`, `index.ts`, package/tsconfig scaffolding, and wrapping legacy scripts in `createSync()`, `createAction()`, or `createOnEvent()`. Use when a Nango repo still has `nango.yaml` and needs manual Zero YAML migration.