Skip to main content
This guide shows you how to build a Google Docs-style collaborative text editor using CollabKit’s Yjs provider with TipTap.

What You’ll Build

  • A rich text editor where multiple users can type simultaneously
  • Conflict-free collaborative editing via CRDTs
  • Persistent document state (survives page refreshes)
  • User cursors and presence indicators

Prerequisites

  • A CollabKit server running with a room and users created
  • @collab-kit/client installed

Step 1: Install Dependencies

npm install @collab-kit/client @collab-kit/utils yjs \
  @tiptap/core @tiptap/starter-kit @tiptap/extension-collaboration

Step 2: Set Up CollabKit

import CollabKitClient from '@collab-kit/client';

const client = new CollabKitClient({
  serverUrl: 'https://api.collab-kit.com',
  authToken: '<jwt-token>',
});

await client.connect();
await client.join();

Step 3: Create the Yjs Document and Provider

import { CollabKitYjsProvider } from '@collab-kit/client/yjs';
import * as Y from 'yjs';

const ydoc = new Y.Doc();
const provider = new CollabKitYjsProvider(client, ydoc, {
  documentId: 'main-editor', // Unique per document in the room
});

Step 4: Initialize TipTap

import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import Collaboration from '@tiptap/extension-collaboration';

const editor = new Editor({
  element: document.getElementById('editor'),
  extensions: [
    StarterKit.configure({
      history: false, // Disable built-in undo -- Yjs handles this
    }),
    Collaboration.configure({
      document: ydoc,
    }),
  ],
  editable: false, // Start disabled until synced
});

// Enable editing once the initial document state loads
provider.on('synced', () => {
  editor.setEditable(true);
  console.log('Document ready for editing');
});
Always disable TipTap’s built-in history extension when using Collaboration. Yjs provides its own undo/redo management that understands collaborative edits.

Step 5: Add a Loading State

Show a loading indicator until the document syncs:
const loadingEl = document.getElementById('loading');

provider.on('synced', () => {
  loadingEl.style.display = 'none';
  editor.setEditable(true);
});

Step 6: Add User Presence (Optional)

Show cursor positions of other users alongside the editor:
// Share cursor/selection position
editor.on('selectionUpdate', ({ editor }) => {
  const { from, to } = editor.state.selection;
  client.presence.update({
    cursor: { from, to },
    user: { name: client.currentUser?.name },
  });
});

// Display other users' selections
client.presence.sync('*', ({ userId, state }) => {
  if (userId === client.userId || !state) return;
  // Use state.cursor.from and state.cursor.to to highlight
  // their selection in the editor (implementation depends on your UI)
  console.log(`${state.user.name} is at position ${state.cursor.from}-${state.cursor.to}`);
});

Step 7: Clean Up

window.addEventListener('beforeunload', () => {
  provider.destroy();
  editor.destroy();
  void client.disconnect();
});

Multiple Documents Per Room

You can have multiple collaborative documents in the same room by using different documentId values:
const notesDoc = new Y.Doc();
const notesProvider = new CollabKitYjsProvider(client, notesDoc, {
  documentId: 'meeting-notes',
});

const agendaDoc = new Y.Doc();
const agendaProvider = new CollabKitYjsProvider(client, agendaDoc, {
  documentId: 'meeting-agenda',
});

Next Steps