Files
upstream-mirror aa862d2a17
Some checks failed
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
i18n upload strings / Upload latest strings (push) Failing after 0s
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 0s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 0s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 0s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Close inactive issues / close-issues (push) Failing after 3s
i18n download strings / Download latest strings (push) Failing after 0s
Post release marketing summary to Discord / Post release marketing summary to Discord (push) Failing after 0s
Post release crew to Discord / Post release crew to Discord (push) Failing after 0s
Prune preview deploys / Prune preview deploys (push) Failing after 1s
Staging E2E Daily Cleanup / Daily Cleanup of Test Files (push) Failing after 1s
top-up: 8 source files 128-600 KB
2026-09-23 16:16:53 +05:00
..

tldraw sync server

This is a production-ready backend for tldraw sync.

  • Your client-side tldraw-based app can be served from anywhere you want.
  • This backend uses Cloudflare Workers, and will need to be deployed to your own Cloudflare account.
  • Each whiteboard is synced via WebSockets to a Cloudflare Durable Object, which persists room state in its built-in SQLite storage.
  • Uploaded images and videos are stored in a Cloudflare R2 bucket.
  • Although unrelated to tldraw sync, this server also includes a component to fetch link previews for URLs added to the canvas. This is a minimal setup of the same system that powers multiplayer collaboration for hundreds of thousands of rooms & users on www.tldraw.com. Because durable objects effectively create a mini server instance for every single active room, we've never needed to worry about scale. Cloudflare handles the tricky infrastructure work of ensuring there's only ever one instance of each room, and making sure that every user gets connected to that instance. We've found that with this approach, each room is able to handle about 50 simultaneous collaborators.

architecture

When a user opens a room, they connect via Workers to a durable object. Each durable object is like its own miniature server. There's only ever one for each room, and all the users of that room connect to it. When a user makes a change to the drawing, it's sent via a websocket connection to the durable object for that room. The durable object applies the change to its in-memory copy of the document, and broadcasts the change via websockets to all other connected clients. Room state is persisted automatically to the durable object's built-in SQLite storage, so it survives restarts and hibernation. When the last client leaves the room, the durable object will shut down.

Static assets like images and videos are too big to be synced via websockets and a durable object. Instead, they're uploaded to workers which store them in an R2 bucket. When they're downloaded, they're cached on cloudflare's edge network to reduce costs and make serving them faster.

Development

To install dependencies, run yarn. To start a local development server, run yarn dev. This will start a vite dev server running both your application frontend, and the cloudflare workers backend via the cloudflare vite plugin. The app & server should now be running at http://localhost:5137.

The backend worker is under worker, and is split across several files:

The frontend client is under client:

  • client/App.tsx: the main client <App /> component. This connects our sync backend to the <Tldraw /> component, wiring in assets and bookmark previews.

  • client/multiplayerAssetStore.tsx: how does the client upload and retrieve assets like images & videos from the worker?

  • client/getBookmarkPreview.tsx: how does the client fetch bookmark previews from the worker?

    Custom shapes

To add support for custom shapes, see the tldraw sync custom shapes docs.

Adding cloudflare to your own repo

If you already have an app using tldraw and want to use the system in this repo, you can copy and paste the relevant parts to your own app.

To add the server to your own app, copy the contents of the worker folder and ./wrangler.toml into your app. Add the dependencies from package.json. You can run the worker using wrangler dev in the same folder as ./wrangler.toml.

To point your existing client at the server defined in this repo, copy client/multiplayerAssetStore.tsx and client/getBookmarkPreview.tsx into your app. Then, adapt the code from client/App.tsx to your own app. Adapt the /api/ URLs used in each of these files to point at your new wrangler dev server.

Deployment

To deploy this example, you'll need to create a cloudflare account and create an R2 bucket to store uploaded images and videos. Update bucket_name = 'tldraw-content' in wrangler.toml with the name of your new bucket.

To actually deploy the app, first create a production build using yarn build. Then, run yarn wrangler deploy. This will deploy the backend worker along with the frontend app to cloudflare. This should give you a workers.dev URL, but you can also configure a custom domain.

License

This project is provided under the MIT license found here. The tldraw SDK is provided under the tldraw license.

Trademarks

Copyright (c) 2024-present tldraw Inc. The tldraw name and logo are trademarks of tldraw. Please see our trademark guidelines for info on acceptable usage.

Distributions

You can find tldraw on npm here.

Contribution

Found a bug? Please submit an issue.

Community

Have questions, comments or feedback? Join our discord. For the latest news and release notes, visit tldraw.dev.

Contact

Find us on Twitter/X at @tldraw.