Sign in

PlanetScale + Liveblocks

PlanetScale provides a serverless MySQL database with horizontal scaling and branching. Using webhooks, you can set up one-way synchronization of your Liveblocks data to PlanetScale for reporting, search, audit logs, or app workflows.

How data sync works

Liveblocks webhooks trigger when certain events happen, such as when a collaborative document updates. Liveblocks can trigger an endpoint in your back end, and from here, you can fetch the latest data and write it to PlanetScale. Here’s an example of how it works with Liveblocks Storage.

Edits document storageUpdated webhook getStorageDocument API Storage data Upsert row data 200 OK Throttle User Liveblocks app Webhook endpoint PlanetScale

Which data can be synced?

Various types of Liveblocks data can be synched to PlanetScale with webhooks.

NameDescriptionRelevant webhookRelevant API
RoomsCreated rooms and metadata.roomUpdatedgetRoom
Active usersCurrently connected users.userEnteredgetActiveUsers
Liveblocks StorageCustom realtime document state.storageUpdatedgetStorageDocument
React FlowFlowchart state.storageUpdatedmutateFlow
YjsY.Doc document state.ydocUpdatedgetYjsDocument
Tiptap/BlockNote/LexicalText editor state.ydocUpdatedgetYjsDocument
ThreadsComments, reactions, more.threadCreatedgetThread
This is a summary

This is not an exhaustive list—around 15 related webhook events are available, along with many Node.js methods, Python functions, and REST API endpoints.

Setup

Quickstart for synching Liveblocks data to PlanetScale. In this example, we sync Liveblocks Storage data to PlanetScale, but you can use the same pattern with other APIs and webhooks to sync other types of data.

Step-by-step guide

We have a full step-by-step guide available here, this page provides a quick summary.

  1. Create the PlanetScale table

    Use one row for each Liveblocks room.

    create table liveblocks_documents (  room_id varchar(255) primary key,  data json not null,  updated_at timestamp not null default current_timestamp    on update current_timestamp);
  2. Create a webhook endpoint

    Add a back end endpoint in your app, for example at /api/liveblocks-webhook.

    export async function POST(request: Request) {  const body = await request.json();  const headers = request.headers;
    // Verify the webhook event, then sync to PlanetScale // ...
    return new Response(null, { status: 200 });}
  3. Subscribe to Storage updates

    In the Liveblocks dashboard, navigate to the “Webhooks’ page inside a project,
    and create a webhook endpoint for your endpoint URL—this requires you to host your local project. Subscribe to storageUpdated, then copy the webhook secret.

  4. Verify the webhook event

    In your endpoint, using WebhookHandler, verify the webhook event with the webhook secret from the dashboard.

    import { WebhookHandler } from "@liveblocks/node";
    const webhookHandler = new WebhookHandler( process.env.LIVEBLOCKS_WEBHOOK_SECRET!);
    export async function POST(request: Request) { const body = await request.json(); const headers = request.headers;
    // Verify if this is a real webhook request let event; try { event = webhookHandler.verifyRequest({ headers: headers, rawBody: JSON.stringify(body), }); } catch (err) { console.error(err); return new Response("Could not verify webhook call", { status: 400 }); }
    // Sync to PlanetScale // ...
    return new Response(null, { status: 200 });}
  5. Sync Storage to PlanetScale

    Set up your Liveblocks and PlanetScale clients, before fetching the Storage document data with getStorageDocument and upserting the PlanetScale row with on duplicate key update.

    import { Liveblocks, WebhookHandler } from "@liveblocks/node";import { connect } from "@planetscale/database";
    const liveblocks = new Liveblocks({ secret: process.env.LIVEBLOCKS_SECRET_KEY!,});
    const db = connect({ url: process.env.DATABASE_URL! });
    const webhookHandler = new WebhookHandler( process.env.LIVEBLOCKS_WEBHOOK_SECRET!);
    export async function POST(request: Request) { const body = await request.json(); const headers = request.headers;
    // Verify if this is a real webhook request let event; try { event = webhookHandler.verifyRequest({ headers: headers, rawBody: JSON.stringify(body), }); } catch (err) { console.error(err); return new Response("Could not verify webhook call", { status: 400 }); }
    if (event.type === "storageUpdated") { const { roomId } = event.data;
    // Get Storage document data const data = await liveblocks.getStorageDocument(roomId, "json");
    // Upsert into PlanetScale await db.execute( `insert into liveblocks_documents (room_id, data) values (?, ?) on duplicate key update data = values(data)`, [roomId, JSON.stringify(data)] ); }
    return new Response(null, { status: 200 });}
  6. Data sync is set up!

    Your Liveblocks data is now automatically synched to PlanetScale when the webhook event is fired.

Limits and troubleshooting

Storage or Yjs data is stale

storageUpdated and ydocUpdated webhooks are throttled because collaborative documents can be modified up to 60 times per second. Treat PlanetScale as an eventually consistent mirror, not as the live editing channel.

Webhook verification fails

Check that LIVEBLOCKS_WEBHOOK_SECRET is the webhook secret for the webhook endpoint that sent the event. Also make sure your endpoint passes the same raw body string to verifyRequest that it received from Liveblocks.

PlanetScale writes fail

Keep DATABASE_URL on the server only. Use a database password with permission to write the mirror tables. PlanetScale doesn’t support foreign keys by default—if you need referential integrity, model it in application code.

Duplicate writes happen

Webhook deliveries can be retried. Use on duplicate key update upserts with a stable primary key such as room_id, thread_id, or comment_id so repeated deliveries update the same row.

Liveblocks REST requests fail

Check that LIVEBLOCKS_SECRET_KEY is a secret key from the same Liveblocks project as the room. If the request still fails, return a non-2xx response so the webhook can be retried.

Related docs