A sync pulls data from external sources like Salesforce, Stripe, and GitHub and writes it to a Notion database. You define a schema for the database and anDocumentation Index
Fetch the complete documentation index at: https://developers.notion.com/llms.txt
Use this file to discover all available pages before exploring further.
execute function that returns the data. Notion runs it on a schedule and manages the database for you.
Define a database and sync
Every sync needs a database to write to. Declare one withworker.database(), then register a sync that targets it:
src/index.ts
primaryKeyProperty tells Notion which property uniquely identifies each row. This is typically the entity’s ID in the external API (e.g., a Salesforce Contact ID or GitHub issue ID). When your sync emits a record with the same key, Notion updates the existing row instead of creating a duplicate.
Schema and properties
Theschema.properties object defines the columns of your Notion database. Each property uses a Schema helper to declare its type, and each upsert uses the corresponding Builder helper to set its value.
For the full list of supported property types, see Schema and builders.
Set page icons and covers
Each upsert can optionally set page metadata withicon and cover. Use Builder.imageCover() with an external image URL and an optional position from 0 (top) to 1 (bottom):
Choose a sync mode
Workers support two sync modes. Pick the one that fits your needs:- Replace (default)
- Incremental
Each sync cycle returns the full dataset. After the final
hasMore: false, any rows not seen during that cycle are automatically deleted.Best for smaller datasets (under 10k records) or APIs that don’t support change tracking. Also used as the backfill half of a backfill + delta pair.Paginate large datasets
When syncing more than a few hundred records, break the work into batches. The runtime callsexecute repeatedly until you return hasMore: false:
- Return a batch of changes with
hasMore: trueand anextStatevalue. - The runtime calls
executeagain, passing that state back as the first argument. - Repeat until you return
hasMore: false.
nextState can be any serializable value, such as a cursor string, page number, timestamp, or object. Start with batch sizes of ~100 records.
Set a schedule
A schedule controls how often Notion triggers your sync. Each time it triggers, the runtime callsexecute repeatedly until it returns hasMore: false, then waits for the next scheduled trigger. The default schedule is every 30 minutes.
| Value | Behavior |
|---|---|
"5m", "15m", "1h", "1d" | Run at the given interval |
"manual" | Only run when triggered via the CLI |
Minimum schedule is
"5m", maximum is "7d".Combine backfill and delta syncs
A single replace sync works for small datasets, but most real integrations need two things: fast updates (minutes, not hours) and the ability to re-sync everything when needed. You get both by registering two syncs against the same database:- A delta sync runs on a schedule and fetches only what changed since the last run. This keeps the database near-real-time.
- A backfill sync paginates the entire upstream dataset. You trigger it manually, for example after a schema change, to populate a new property, or to catch anything the delta missed.
| Delta sync | Backfill sync | |
|---|---|---|
| Mode | incremental | replace |
| Schedule | "5m" or "30m" | "manual" |
| What it does | Grabs recent changes via updated_since or a change feed | Paginates the entire upstream dataset |
| Deletes | Emits type: "delete" if the API supports it | Mark-and-sweep catches everything else |
| When it runs | Continuously on schedule | On demand |
Relate two databases
Link databases together withSchema.relation() and Builder.relation():
Schema.relation("projects") references the database name projects from worker.database("projects", ...), and the twoWay: true option adds a “Tasks” rollup column to the Projects database automatically.
Authenticate with external APIs
Most syncs need credentials for the external API they pull from. You have two options:- API keys and tokens: store them as secrets and read from
process.env. - OAuth: for APIs that require user authorization (GitHub, Google, Salesforce), register an OAuth capability and call
accessToken()in yourexecutefunction.
Rate-limit outbound requests
Use a pacer to avoid hitting third-party API rate limits:await api.wait() blocks until a request slot is available. In this example, at most 10 requests per second.
Manage syncs from the CLI
Deploying does not reset sync state. Syncs resume from their last cursor position. See Resetting and migrating state below.
Reset and migrate state
Deploys never clear sync state. Your sync picks up where it left off. If you need to start fresh (e.g., after changing your schema or fixing a bug in yourexecute function), reset the state:
nextState so the next run starts from scratch, as if the sync had never run before.
To inspect the current state before deciding whether to reset:
Troubleshooting syncs
Sync runs but no rows appear
- Check
ntn workers sync trigger <syncKey> --previewto see what yourexecutefunction returns without writing to the database. If the preview is empty, the issue is in your data-fetching code. - Make sure the
keyin each change matches the property named byprimaryKeyProperty.
Rows are duplicated
- Each row needs a unique
key. If two changes share the same key, the second overwrites the first. If keys differ, Notion creates separate rows. Double-check that your key is the stable external ID, not a value that changes between runs.
Stale rows aren’t deleted (replace mode)
- Replace mode only deletes stale rows after the final page returns
hasMore: false. If your sync errors partway through, no deletions happen (this is intentional to avoid data loss).
Sync is stuck or out of date
- Run
ntn workers sync statusto see the current state and last run time. - If state is corrupted or outdated, reset it with
ntn workers sync state reset <syncKey>.
Checking logs
List recent runs:ntn workers sync flags and options.
Next steps
Schema and builders
Full reference for database property types and value builders.
SDK reference
Detailed API docs for worker.sync(), worker.database(), and worker.pacer().
Secrets
Store API keys and credentials for your sync.
OAuth
Connect to APIs that require user authorization.