slash-editor
Guides

Collaboration

Real-time collaboration over Yjs — self-hosted, no paid tier.

collaboration wraps Tiptap's official Collaboration/CollaborationCaret extensions over y-prosemirror. It has no default and forces history: false once set (Yjs owns undo/redo):

Prop

Type

Provider contract

CollaborationProvider is duck-typed against the shared-awareness surface — HocuspocusProvider, WebsocketProvider, and WebrtcProvider all satisfy it without an adapter:

interface CollaborationProvider {
  awareness: {
    setLocalStateField(field: string, value: unknown): void;
    getStates(): Map<number, Record<string, unknown>>;
    on(event: "update" | "change", listener: () => void): void;
    off(event: "update" | "change", listener: () => void): void;
  };
}

Remote carets render as data-collab-caret/data-collab-caret-label/data-collab-selection — the core emits no class names, so style them directly. See Presence Avatars for reading the same awareness instance into a UI list of connected collaborators.

Peer-to-peer over WebRTC (what the playground uses)

The playground's own ?collab=<room> mode uses y-webrtc's WebrtcProvider — true peer-to-peer, no document server of ours to run:

import * as Y from "yjs";
import { WebrtcProvider } from "y-webrtc";

const document = new Y.Doc();
const provider = new WebrtcProvider(room, document, {
  signaling: ["wss://your-signaling-server"],
});

useSlashEditor({
  blockKit: {
    collaboration: {
      document,
      provider, // enables CollaborationCaret presence carets
      user: { name: "Ada Lovelace", color: "#94A3B8" },
    },
  },
});

Peers still need a signaling server to find each other — a small, stateless relay that forwards only WebRTC connection setup (SDP/ICE), never document content. y-webrtc ships one default list of public signaling servers, but as of this writing they're dead (the Heroku free dynos they ran on were decommissioned). This repo runs its own instead, as a Vercel WebSocket Function — see app/api/signaling/route.ts, a direct port of y-webrtc's own bin/server.js protocol. Point signaling at your own instance (self-hosted with y-webrtc's bundled server, or your own port of the same protocol) rather than relying on the upstream defaults.

WebRTC doesn't suit every deployment: it's a full mesh (each peer connects to every other), so it degrades past a few dozen concurrent collaborators on one document, and some restrictive corporate networks block the UDP traffic WebRTC needs. For those cases, use the centralized Hocuspocus recipe below instead — the collaboration() extension doesn't care which one you use.

Self-hosting a central server instead

For a document with many concurrent editors, or networks where peer-to-peer isn't reliable, run a central Hocuspocus server instead — the same CollaborationProvider contract, backed by a real server rather than a mesh:

import * as Y from "yjs";
import { HocuspocusProvider } from "@hocuspocus/provider";

const document = new Y.Doc();
const provider = new HocuspocusProvider({ url: "ws://localhost:1234", name: room, document });

useSlashEditor({
  blockKit: {
    collaboration: { document, provider, user: { name: "Ada Lovelace", color: "#94A3B8" } },
  },
});

There is no hosted collaboration backend to pay for either way. server/collab-server.ts in the repository is the reference Hocuspocus implementation this pattern runs against.

On this page