Official agent skill

Notion Workers Sync Guide

by makenotion in makenotion/workers-template

Guides the design of Notion Workers syncs, from choosing a simple replace sync or a backfill plus delta pair to pagination, consistency buffers, pacing and deletion handling.

OfficialMITAuto-check passedBackend & APIs

Install Notion Workers Sync Guide

skills CLI
$ npx skills add makenotion/workers-template --skill sync-guide -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install makenotion/workers-template sync-guide --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/makenotion/workers-template.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/sync-guide .claude/skills/sync-guide && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
sync-guide
GitHub stars
439
Used in
1 other repo
Token cost
~3k tokens
SKILL.md length
1,008 words
Files
7
Skills in repo
5
Repo updated
First seen
Licence
MIT

At a glance

Guides the design of Notion Workers syncs, from choosing a simple replace sync or a backfill plus delta pair to pagination, consistency buffers, pacing and deletion handling.

  • Works in 4 steps: Choose an Architecture → Understand Your API's Pagination → Consistency Buffer (Delta Syncs) → …
  • Building a Notion Workers sync from an external API
  • SKILL.md covers What is a Sync?, Decision Framework, Replace Mode and Incremental Mode (Delta Sync), plus 4 more sections
  • Runs TypeScript scripts from its folder

What it does

A sync is a recurring execute function that returns changes, a hasMore flag and the next state to fill a Notion database, and the runtime loops until hasMore is false to complete one cycle. The skill's first decision is architecture. A small source of under 1k records, or an API without change tracking, uses one replace sync that deletes unseen records after the last page. Anything with updated timestamps or change feeds uses a backfill and delta pair writing to one database: a manual replace-mode backfill and a frequent incremental delta.

Safeguards include initializing the delta cursor before the backfill starts and putting an upstreamUpdatedAt value on upserts so a stale backfill cannot overwrite newer delta data. Pagination advice is to return batches of about 100 changes and to recognize opaque cursors, page numbers or offsets and keyset paging. Example TypeScript files show incremental, bimodal, event-based and replace-based syncs. The description adds pacers, deletion strategies and pitfalls, which fall in the cut-off part of the excerpt.

When your agent uses it

  • Building a Notion Workers sync from an external API
  • Choosing between a replace sync and a backfill plus delta pair
  • Paginating a large API without exceeding the per-call change limit
  • Preventing stale backfill data from overwriting recent updates

Example prompts

  • “Design a Notion Workers sync for our CRM API that supports updated_at filtering.”
  • “Our source has only a few hundred records and no change feed; which sync mode should I use?”
  • “Write a keyset-paginated backfill sync for the issues API.”
  • “Add upstreamUpdatedAt to the upserts so the backfill cannot overwrite delta changes.”

Requirements

  • A Notion Workers project using the @notionhq/workers package

Workflow steps

4 steps, taken from the step headings in SKILL.md.

  1. Choose an Architecture
  2. Understand Your API's Pagination
  3. Consistency Buffer (Delta Syncs)
  4. Deletion Strategies

What it can do on your machine

Read from SKILL.md and the folder at commit 681d89d. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships script files (TypeScript), which the agent can run.

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Notion Workers Sync Guide loads about 3k tokens when it runs. Until then it costs about 65 tokens; SKILL.md has 1,008 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~65
When it runs · the whole SKILL.md, loaded when a task matches
~3k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from makenotion/workers-template at commit 681d89d, republished under its MIT licence (© makenotion). 1,008 words, ~2,996 tokens.

Download SKILL.mdSave it as .claude/skills/sync-guide/SKILL.md (or your agent's skills folder). This skill also uses 6 other files; get the full folder from GitHub.
name
sync-guide
description
Comprehensive guide to building Notion Workers syncs — covers the two-sync architecture (backfill+delta), replace mode, pagination, consistency buffers, pacers, deletion strategies, and common pitfalls. Auto-loads when sync-related work is detected.
user-invocable
false

What is a Sync?

A sync is a recurring execute function that returns data changes to populate a Notion database. The runtime calls execute in a loop:

ts
const db = worker.database("myDb", {
  type: "managed",
  initialTitle: "My Data",
  primaryKeyProperty: "ID",
  schema: {
    properties: {
      Name: Schema.title(),
      ID: Schema.richText(),
    },
  },
});

worker.sync("mySync", {
  database: db,
  execute: async (state, { notion }) => ({
    changes: [
      { type: "upsert", key: "1", properties: { Name: Builder.title("Item 1"), ID: Builder.richText("1") } },
    ],
    hasMore: false,
    nextState: undefined,
  }),
});

Each call returns { changes, hasMore, nextState }. If hasMore is true, the runtime calls execute again with nextState. This continues until hasMore is false, completing a cycle. The next cycle begins at the scheduled interval with the state from the end of the previous cycle.

Imports:

ts
import { Worker } from "@notionhq/workers";
import * as Builder from "@notionhq/workers/builder";
import * as Schema from "@notionhq/workers/schema";

Decision Framework

Step 1: Choose an Architecture

The deciding factor is API capability and dataset size. Two tiers:

ConditionArchitecture
Small source (<1k records) or API with no change trackingSimple replace sync — one sync, mode: "replace"
Everything else (API supports updated_at, change feeds, events)Backfill + delta pair — two syncs writing to the same database

Simple replace sync: One sync returns the full dataset each cycle. After the final hasMore: false, any records not seen are deleted automatically. Use when the dataset is small enough to re-fetch entirely.

Backfill + delta pair: Two syncs share a single database. The backfill sync (mode: "replace", schedule: "manual") re-fetches everything when triggered. The delta sync (mode: "incremental", frequent schedule) fetches only changes since the last run. This separates concerns cleanly — no bi-modal state machine, no backfill-to-delta transition bugs.

Initialize the delta cursor before starting the backfill. When both syncs can write the same record, include upstreamUpdatedAt on each upsert so a stale backfill response cannot overwrite a newer delta response.

Step 2: Understand Your API's Pagination

Most APIs require paginating through results. Return batches of ~100 changes. Returning too many changes in one execute call will fail.

Backfill pagination (full dataset load):

  1. Opaque cursor token — GraphQL endCursor, Stripe starting_after
  2. Page number / offset — ?page=N&limit=100
  3. Keyset (timestamp + id) — WHERE created_at > X OR (created_at = X AND id > Y) — the gold standard for timestamp-sorted mutable data

Delta pagination (change-only loads, incremental mode):

  1. Timestamp cursor — ?updated_since=<cursor> with consistency buffer
  2. Keyset on updated_at + id — same keyset pattern on the modification timestamp
  3. Event/changelog feed — GET /events?after=<eventId>
  4. Same opaque cursor — when the API sorts by updated_at, the backfill cursor works for delta too
Step 3: Consistency Buffer (Delta Syncs)

APIs tend to be eventually consistent. A record that was just written or updated may not appear in query results immediately. Since the cursor never resets in incremental mode, if it advances past a record that hasn't been indexed yet, that record is skipped permanently. Lag the cursor 10-60 seconds behind "now":

ts
const bufferMs = 15_000;
const maxCursor = new Date(Date.now() - bufferMs).toISOString();
function minTimestamp(a: string, b: string): string {
	return Date.parse(a) <= Date.parse(b) ? a : b;
}

const nextCursor = records.length > 0
  ? minTimestamp(lastRecord.updatedAt, maxCursor)
  : maxCursor;
Step 4: Deletion Strategies
  1. Backfill sync (replace mode): free — unseen records are auto-deleted each cycle. This is the primary mechanism for handling deletes when the API has no delete signal.
  2. Delta sync with delete API: emit { type: "delete", key } markers. If the delete signal comes from a separate endpoint (audit log, archived filter), use the flip-flop pattern: run the main delta stream until caught up (hasMore: false), then switch to the delete stream for a cycle, then back. Both cursors persist in state independently.
  3. No delete API, large dataset: rely on the backfill sync's replace-mode mark-and-sweep. Trigger the backfill manually or on a slow schedule to clean up stale records.

Replace Mode

Simple: fetch everything, return it all, let the runtime handle deletes. Use as a standalone sync for small sources, or as the backfill half of a backfill+delta pair.

ts
const db = worker.database("records", {
  type: "managed",
  initialTitle: "Records",
  primaryKeyProperty: "ID",
  schema: {
    properties: { Name: Schema.title(), ID: Schema.richText() },
  },
});

const apiPacer = worker.pacer("myApi", {
  allowedRequests: 10,
  intervalMs: 1000,
});

worker.sync("recordsBackfill", {
  database: db,
  mode: "replace",
  schedule: "manual",  // trigger manually or on a slow schedule
  execute: async (state) => {
    const page = state?.page ?? 1;
    await apiPacer.wait();
    const { items, totalPages } = await fetchPage(page, 100);
    const hasMore = page < totalPages;
    return {
      changes: items.map((item) => ({
        type: "upsert" as const,
        key: item.id,
        properties: { Name: Builder.title(item.name), ID: Builder.richText(item.id) },
      })),
      hasMore,
      nextState: hasMore ? { page: page + 1 } : undefined,
    };
  },
});

See examples/replace-simple.ts and examples/replace-paginated.ts for complete working examples.

Show full SKILL.md (449 more words)Show less

Incremental Mode (Delta Sync)

The delta sync fetches only changes since the last run. When paired with a replace-mode backfill sync on the same database, this replaces the old bi-modal single-sync pattern.

ts
// Reuses the same `db` and `apiPacer` from above

worker.sync("recordsDelta", {
  database: db,
  mode: "incremental",
  schedule: "5m",
  execute: async (state: { cursor: string } | undefined) => {
    const cursor = state?.cursor ?? new Date(0).toISOString();
    const bufferTs = new Date(Date.now() - 15_000).toISOString();

    await apiPacer.wait();
    // fetchChanges must apply bufferTs as an upstream upper bound.
    const { items, nextCursor } = await fetchChanges(cursor, bufferTs);
    const done = !nextCursor;

    return {
      changes: items.map(toUpsert),
      hasMore: !done,
      nextState: {
        // This example assumes nextCursor is an ISO timestamp. Opaque cursors
        // must not be compared with the timestamp buffer.
        cursor: done ? minTimestamp(nextCursor ?? cursor, bufferTs) : nextCursor,
      },
    };
  },
});

Key points:

  • The delta sync's state is simple — just a cursor. No phase discrimination needed.
  • The backfill sync (replace mode) handles the initial full load and periodic cleanup of deleted records.
  • Both syncs write to the same database via the shared db handle.
  • The pacer is shared between syncs — the server apportions the budget evenly.

See examples/incremental-basic.ts, examples/incremental-bimodal.ts, and examples/incremental-events.ts for complete patterns.

Schema Reference

Define the Notion database shape with Schema types and build values with Builder:

Schema typeBuilder valueNotes
Schema.title()Builder.title("text")Primary display field. Every schema needs exactly one.
Schema.richText()Builder.richText("text")Text content, IDs
Schema.url()Builder.url("https://...")URL field
Schema.email()Builder.email("a@b.com")Email field
Schema.phoneNumber()Builder.phoneNumber("+1...")Phone field
Schema.checkbox()Builder.checkbox(true)Boolean
Schema.file()Builder.file("https://...", "name")File URL + optional display name
Schema.number()Builder.number(42)Number. Optional format: Schema.number("percent")
Schema.date()Builder.date("2024-01-15")Date (YYYY-MM-DD). Also: Builder.dateTime("2024-01-15T10:30:00Z"), Builder.dateRange(start, end)
Schema.select([...])Builder.select("Option A")Single select. Define options: Schema.select([{ name: "A" }, { name: "B" }]). Options must have non-empty name values — Schema.select([]) and { name: "" } are not supported.
Schema.multiSelect([...])Builder.multiSelect("A", "B")Multi select
Schema.status(...)Builder.status("Done")Status with groups
Schema.people()Builder.people("email@co.com")People by email
Schema.place()Builder.place({ lat: 40.7, lon: -74.0 })Geographic location
Schema.relation("databaseKey")[Builder.relation("pk")]Relation to another managed database. Value is an array.

Relations use the related database key. Two-way relations are configured the same way:

ts
Schema.relation("otherDatabase", { twoWay: true, relatedPropertyName: "Back Link" })

Row-level icons and page content:

ts
changes: [{
  type: "upsert", key: "1",
  properties: {
    Name: Builder.title("Example"),
    // ... other properties ...
  },
  icon: Builder.emojiIcon("🎯"),               // or Builder.notionIcon("rocket", "blue")
  pageContentMarkdown: "## Details\nSome text", // Markdown body for the page
}]

Common Mistakes

  1. Not using a pacer — every API call inside execute should be preceded by await apiPacer.wait(). Without it, syncs will hit rate limits and fail.
  2. Missing consistency buffer on delta syncs — the cursor will permanently skip records not yet indexed in eventually consistent APIs.
  3. Not paginating — returning too many changes at once. Start with batches of ~100.
  4. Using replace mode for large datasets — if the API supports change tracking, pair a replace-mode backfill sync with an incremental delta sync instead of re-fetching everything each cycle.
  5. Cursor that doesn't advance — infinite loop. Ensure nextState changes between iterations.
  6. Forgetting first-run handling — state is undefined on first call. Use state?.cursor ?? null.
  7. Forgetting that backfill + delta share a database — both syncs must use the same worker.database() handle and the same key/properties shape.
  8. Not triggering the backfill sync — the backfill sync with schedule: "manual" won't run automatically. Trigger it on deploy or periodically to clean up deleted records.
  9. Empty select values — Schema.select() requires at least one option with a non-empty name. Schema.select([]) and { name: "" } are not supported.

CLI Commands for Sync Development

shell
# Deploy
ntn workers deploy

# Preview (test without writing)
ntn workers sync trigger <key> --preview
ntn workers sync trigger <key> --preview --context '<json>'  # continue pagination

# Trigger a sync run
ntn workers sync trigger <key>

# Check sync status
ntn workers sync status

# View run logs
ntn workers runs list
ntn workers runs list --plain | head -n1 | cut -f1 | xargs -I{} ntn workers runs logs {}

# Reset state (full re-backfill)
ntn workers sync state reset <key>

# Manage secrets
ntn workers env set KEY=value
ntn workers env push

API Patterns Reference

See api-pagination-patterns.md for detailed strategies drawn from production syncs with Salesforce, Stripe, HubSpot, GitHub, and ServiceNow.

© makenotion, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 6 other files in .agents/skills/sync-guide of makenotion/workers-template.

  • SKILL.md
  • api-pagination-patterns.md
  • examples/incremental-basic.ts
  • examples/incremental-bimodal.ts
  • examples/incremental-events.ts
  • examples/replace-paginated.ts
  • examples/replace-simple.ts

Open the folder on GitHubat commit 681d89d

Used in 1 other repository

We found 1 copy of this SKILL.md (exact, near-identical or edited) in other folders, from 1 other GitHub owner. This page covers the copy in makenotion/workers-template, which our catalogue first saw on October 7, 2026.

Compare with similar skills

Notion Workers Sync Guide next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Notion Workers Sync Guide compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Notion Workers Sync Guide this skillmakenotion/workers-template4391 repos~3kAutomated safety check: PassMIT
Dinobase Connector Builderkappa90/dinobase263—~1.9kAutomated safety check: PassCustom licence
API Integrationsickn33/agentic-awesome-skills47k1 repos~1.3kAutomated safety check: PassMIT
Integration PatternsJoelLewis/finance_skills205—~10kAutomated safety check: PassMIT
Finta SDK Patternsjeremylongshore/tons-of-skills-marketplace2.8k—~1.2kAutomated safety check: PassMIT
LangBot EBA Adapter Developmentlangbot-app/LangBot18k—~4kAutomated safety check: PassApache-2.0

Similar skills

  • Writes a new Dinobase YAML connector for a REST API that has no verified dlt source, covering auth, pagination, read and write endpoints and incremental loading.

    263 GitHub stars~1.9k tokensUpdated 3 mo ago
    Backend & APIsAuto-check passed
  • API Integration

    sickn33/agentic-awesome-skills

    Designs event-driven architectures, webhook systems, API chaining flows, ETL pipelines, and integration patterns between services.

    47k GitHub starsUsed in 1 repo~1.3k tokens
    Backend & APIsAuto-check passed
  • Integration Patterns

    JoelLewis/finance_skills

    Design and implement integration architectures connecting financial systems — APIs, FIX protocol, ISO 20022, event-driven patterns, batch feeds, idempotency, and resilience.

    205 GitHub stars~10k tokensUpdated 2 mo ago
    Backend & APIsAuto-check passed
  • Finta SDK Patterns

    jeremylongshore/tons-of-skills-marketplace

    Integration patterns for Finta fundraising CRM with email and calendar APIs.

    2.8k GitHub stars~1.2k tokensUpdated today
    Backend & APIsAuto-check passed
  • Guides building, migrating and testing LangBot messaging-platform adapters for the Event-Based Agents layout, with unified event and message conversion.

    18k GitHub stars~4k tokensUpdated today
    Backend & APIsAuto-check passed
  • Tushare Plugin Builder

    Yourdaylight/stock_datasource

    Turns a Tushare API doc URL into a full data plugin for the stock_datasource repo: extractor, ClickHouse schema, query service, config and curl examples.

    188 GitHub stars~2.5k tokensUpdated 1 mo ago
    Data & AnalyticsAuto-check passed

More from makenotion/workers-template

  • Notion Worker Third-Party Auth Guide

    makenotion/workers-template

    Official

    Decides whether a Notion Worker should use a brokered credential, a plaintext environment secret, or OAuth to authenticate against a non-Notion service.

    439 GitHub starsUsed in 1 repo~3.5k tokens
    Auto-check: notes
  • Notion Worker Sync Scaffold

    makenotion/workers-template

    Official

    Walks you through designing a new sync for a Notion Worker, covering data source, mode, pagination and cursors, and then generates working code.

    439 GitHub starsUsed in 1 repo~4.6k tokens
    Auto-check: notes
  • Workers Sync Debugger

    makenotion/workers-template

    Official

    Works out why a Workers sync is failing or returning wrong data by reading run logs through the ntn CLI, matching errors to the sync code and proposing fixes.

    439 GitHub starsUsed in 1 repo~957 tokens
    Auto-check: notes
  • Sync Capability Validator

    makenotion/workers-template

    Official

    Checks a Workers sync capability against a list of common bugs, such as stuck cursors, endless pagination and lost deletions, and reports by severity.

    439 GitHub starsUsed in 1 repo~1.2k tokens
    Auto-check: notes

Works with

Questions about Notion Workers Sync Guide

What does Notion Workers Sync Guide do?

Guides the design of Notion Workers syncs, from choosing a simple replace sync or a backfill plus delta pair to pagination, consistency buffers, pacing and deletion handling. A sync is a recurring execute function that returns changes, a hasMore flag and the next state to fill a Notion database, and the runtime loops until hasMore is false to complete one cycle. The skill's first decision is architecture.

When should I use Notion Workers Sync Guide?

Notion Workers Sync Guide fits situations like: building a Notion Workers sync from an external API; choosing between a replace sync and a backfill plus delta pair; paginating a large API without exceeding the per-call change limit; preventing stale backfill data from overwriting recent updates.

How do I install Notion Workers Sync Guide in Claude Code?

Run `npx skills add makenotion/workers-template --skill sync-guide -a claude-code`. Or copy the skill folder (.agents/skills/sync-guide in makenotion/workers-template) into .claude/skills/sync-guide in your project. Claude Code loads it when a task matches its description.

How do I install Notion Workers Sync Guide in Codex?

Run `npx skills add makenotion/workers-template --skill sync-guide -a codex`. Or copy the skill folder (.agents/skills/sync-guide in makenotion/workers-template) into .agents/skills/sync-guide in your project. Codex loads it when a task matches its description.

Can I use Notion Workers Sync Guide in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add makenotion/workers-template --skill sync-guide -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/sync-guide, .gemini/skills/sync-guide, .github/skills/sync-guide and .opencode/skills/sync-guide in your project.

What does Notion Workers Sync Guide need to run?

Going by SKILL.md and its folder, Notion Workers Sync Guide needs TypeScript for the scripts in its folder. Our summary lists: A Notion Workers project using the @notionhq/workers package.

Does Notion Workers Sync Guide access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Notion Workers Sync Guide safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Notion Workers Sync Guide use?

Notion Workers Sync Guide is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Notion Workers Sync Guide use?

About 3k tokens (SKILL.md is roughly 12k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Notion Workers Sync Guide?

Skills that share tags, products or a category with Notion Workers Sync Guide: Dinobase Connector Builder (kappa90/dinobase, 263 stars), API Integration (sickn33/agentic-awesome-skills, 47k stars), Integration Patterns (JoelLewis/finance_skills, 205 stars) and Finta SDK Patterns (jeremylongshore/tons-of-skills-marketplace, 2.8k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Notion Workers Sync Guide?

makenotion (a GitHub organization, an official publisher) maintains it in makenotion/workers-template, which has 439 GitHub stars. The repository holds 5 skills in this directory. The repository was last updated on September 11, 2026.

Source: makenotion/workers-template on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.