Open Sync is a headless sync engine that handles checkpointing, queuing, and retries. You define how sources read data and how destinations deliver it, so the same engine can fit different providers, storage systems, and workflows.
Embed it as a dependency and call it directly from your host's business logic. The engine runs in the same process; your application keeps its own UI and authentication.
This repository also includes a default host implementation as a sample app: a dashboard with sources for GitHub pull requests, Gmail and Slack threads, and Granola meetings.
Components
| Component | What it does | How to configure it |
|---|---|---|
| External API | The system your source polls. | Choose it in your source logic; connect provider accounts through sync.providers when needed. |
| Source logic | Your code that reads data and returns a deliverable. | Register it in definitions; declare inputs in configSchema and read data in step(). |
| Engine | Coordinates polling, saves checkpoints, queues deliveries, and retries failed work. | Register sources and delivery handlers with createOpenSync(). Your host controls its start() and close() lifecycle. |
| Delivery logic | Your code that writes each delivery to the destination. | Register it in destinationTypes with a configSchema and deliver(). Set acceptsAssets: true to handle assets. |
| Destination | The service, database, or storage that receives your data. | Pass its settings as config to sync.api.createDestination(). |
Data types
| Data | What it is | How to use it |
|---|---|---|
| Record | A structured item, such as an email thread. | Set operation (upsert or delete), kind, and a stable id. Upserts include data matching the source's kinds schema. |
| Asset | Binary content, such as an attachment or image. | Capture it with assets.capture() and include its reference in a record's assetRefs or the deliverable's assets. |
| Deliverable | A batch of records and optional assets produced by a source. | Return it from step() alongside a checkpoint (where to resume) and complete (whether this poll finished). |
The current package requires Bun 1.4+.
bun add @context-use/open-sync- Call
await createOpenSync()with a data directory, your sources and destinations, and your host's authorization functions. - Route requests to
sync.fetch(request)and setpublicUrlto that route's full URL (for example,https://your-app.com/api/open-sync). This also handles provider authorization callbacks. - Call
await sync.start()before accepting requests andawait sync.close()when shutting down.
Startup registers one half-hourly OS job through Bun.cron; Linux hosts need a running cron service.
For a compiled binary, follow the sample entrypoint: handle
runOpenSyncCron() before dynamically importing the normal server code, and compile with
splitting: true. This keeps the temporary cron process from loading the server modules. It asks
the running app to process due syncs, waits for completion, and exits.
Use sync.providers to connect accounts, then create a sync with sync.api.createSync().
Supply the source definition, its config, the destination, and a provider connection
when needed. All enabled syncs poll on the shared cron schedule. Pass the acting user's
actorId and data owner's ownerId from your host's authentication.
See the sample host, configuration options, and source and destination examples.
Use the button above to deploy the sample host on nibrun. Open your instance's URL, create an account, and connect your providers.
To update an existing instance, complete the local setup below, then install the nibrun CLI and sign in:
curl -fsSL https://nibrun.com/install.sh | sh
nib login
bun run deploy --app YOUR_APP_SLUGReplace YOUR_APP_SLUG with the slug from nib apps list. The command builds and deploys the update.
Clone this repository and install Bun 1.4 and Node 24, then:
bun install
bun run devOpen localhost:5173 and create an account.
