Skip to main content
The @notionhq/workers SDK defines a worker manifest in TypeScript. A worker exports one Worker instance, then registers resource declarations and capabilities on that instance.
src/index.ts
For database schemas, property value builders, and tool input schemas, see Schema and builders.

Worker

Worker is the entry point for every worker project. The class exposes methods that add databases, pacers, and capabilities to the worker manifest.

worker.database()

Declares a managed Notion database for sync output.

Managed database lifecycle

Each worker.database() declaration corresponds to a Notion database that the platform creates for the worker. Managed databases are migrated on every deploy. Changing the schema in code and redeploying migrates the database schema in Notion.
Schema migrations can drop data. Review schema changes before deploying, especially when removing or changing existing properties.
Managed databases are currently used only by sync capabilities. They are not generic worker storage, and they are not used by webhooks. The managed database schema must match the property values returned by sync changes. In Notion, properties defined in code are not editable. Users can add additional properties to the database, and those additional properties remain editable in Notion. worker.database() returns an opaque database handle. Pass that handle to worker.sync().

worker.pacer()

Declares a rate limit budget for calls to an external API.
Call await issueTrackerApi.wait() before each external API request in a sync. A pacer spreads requests across the configured window. The server injects a wait-between period, and wait() sleeps the sandbox for that period before the next request proceeds. For example, a pacer configured for 10,000 requests per day spaces those requests across the day instead of allowing the worker to use the full budget immediately. This pacing can underutilise the upstream limit, but it keeps syncs making progress throughout the pacer window. When multiple capabilities use the same pacer, the server calculates the number of concurrently executing capabilities and divides the pace across them.

worker.sync()

Registers a sync capability that writes changes into a database. See the Syncs guide for usage patterns, pagination, and scheduling.

Execution cycle

A sync cycle is one or more execute calls. The runtime calls execute once, applies the returned changes, and calls it again when hasMore is true. The cycle completes when hasMore is false. state is undefined on the first execution. To continue within a cycle, return hasMore: true with a serialisable nextState; the runtime passes that value as state to the next execute call.

State

nextState can be a cursor string, page number, timestamp, or object. Return it whenever the next execution needs a cursor. If hasMore is true, nextState must contain enough information for the next execute call to make progress. Sync state persists across scheduled executions and deploys. For incremental syncs against eventually consistent APIs, keep timestamp cursors slightly behind the current time so recently written upstream records are not skipped permanently:

Modes

Use mode: "replace" for small sources or full backfills. Use mode: "incremental" for delta syncs that fetch only records changed since the last cursor.

Schedule

Use "continuous", "manual", or an interval string ending in m, h, or d. Interval schedules must be at least 1m and at most 7d. If schedule is omitted, the sync runs every 30 minutes.

Sync result

For paginated syncs, return hasMore: true with a serialisable nextState:
Return batches sized for the upstream API and sync runtime. A batch of about 100 changes is a typical starting point.

Sync changes

Use "delete" to remove a record by upstream key:
Delete changes are only applicable in mode: "incremental". In mode: "replace", the runtime deletes records that were not seen by the end of the completed sync cycle.

Multiple syncs for one database

Multiple syncs can write to the same database by passing the same database handle. A common pattern is a manual replace-mode backfill sync plus a scheduled incremental delta sync:
Both syncs must use unique sync keys. When multiple syncs share a pacer, the server apportions the request budget across them. Use upstreamUpdatedAt on upsert changes when multiple syncs can update the same record.

worker.tool()

Registers a tool that can be called by Notion Custom Agents. See the Agent tools guide for a walkthrough of defining inputs, testing locally, and deploying. Tools extend Custom Agent functionality. A worker is attached to a Custom Agent, and each tool declared by that worker can be enabled or disabled on that connection.

Tool hints

Tool hints describe how the tool behaves. readOnlyHint: true marks a tool as read-only and safe to auto-execute. Tools without readOnlyHint: true are treated as write tools and prompt for confirmation.

worker.webhook()

Registers an HTTP webhook endpoint for external services. See the Webhooks guide for request verification, retries, and using the Notion API from a webhook handler.
Throw WebhookVerificationError from execute to signal signature verification failure. After five consecutive verification failures, the platform rejects incoming requests for the webhook without executing the handler.

worker.oauth()

Registers a user-managed OAuth provider and returns an OAuth capability handle. See the OAuth guide for the full setup flow, including provider configuration and testing locally. OAuth capabilities require an OAuth app configured with the external provider. Use the provider’s client ID, client secret, authorisation endpoint, token endpoint, and scopes in the capability configuration. Store credentials as secrets, not in code.

OAuth setup

Use the redirect URL from the CLI when configuring the OAuth app with the provider:
After the worker is deployed, start the three-legged OAuth flow for the OAuth capability:
githubAuth is the capability key passed as the first argument to worker.oauth(). Use accessToken() from tool, sync, or webhook handlers to read the connected token.
After the OAuth flow completes, the server stores the refresh token and refreshes access tokens automatically according to the access token expiry. If the token response does not include expiry information, accessTokenExpireMs supplies the default expiry interval.