The @veltdev/reactflow-crdt library enables real-time collaborative editing on React Flow diagrams. The collaboration editing engine is built on top of Yjs and Velt SDK.

Prerequisites

  • Node.js (v14 or higher)
  • React (v16.8 or higher for hooks)
  • A Velt account with an API key (sign up)
  • Optional: TypeScript for type safety

Setup

Step 1: Install Dependencies

Install the required packages:
npm install @veltdev/reactflow-crdt @veltdev/react

Step 2: Setup Velt

Initialize the Velt client by following the Velt Setup Docs. This is required for the collaboration engine to work.

Step 3: Initialize Velt CRDT Extension

  • Initialize the Velt CRDT extension and use it to initialize the React Flow diagram.
  • Pass an editorId to uniquely identify each diagram instance you have in your app. This is especially important when you have multiple diagrams in your app.
const { nodes, edges, onNodesChange, onEdgesChange, onConnect } = useVeltReactFlowCrdtExtension({
  editorId: 'YOUR_EDITOR_ID',
  initialNodes: [{ id: '1', data: { label: 'Start' }, position: { x: 0, y: 0 } }],
  initialEdges: []
});

return (
  <ReactFlow
    nodes={nodes}
    edges={edges}
    onNodesChange={onNodesChange}
    onEdgesChange={onEdgesChange}
    onConnect={onConnect}
    fitView
  >
    <Background />
  </ReactFlow>
);

Notes

  • Unique editorId: Use a unique editorId per diagram instance.
  • Use CRDT handlers: Always apply the store-provided nodes, edges, onNodesChange, onEdgesChange, and onConnect.

Testing and Debugging

To test collaboration:
  1. Login two unique users on two browser profiles and open the same page in both.
  2. Move nodes or create connections in one window and verify they sync in the other.
Common issues:
  • Nodes/edges not syncing: Ensure each diagram has a unique editorId and the Velt client is initialized. Also ensure that you are logged in with two different users in two different browser profiles.
  • No updates on connect: Use the provided CRDT-aware nodes, edges, onNodesChange/onEdgesChange and onConnect handlers from the store.

Complete Example

See Velt ReactFlow Demo Repo for our complete implementation.
Relevant Code
import {
    Background,
    ReactFlow,
    ReactFlowProvider,
    useReactFlow,
    type Edge
} from '@xyflow/react';
import { useCallback, useRef } from 'react';
import { useVeltInitState } from '@veltdev/react';
import { useVeltReactFlowCrdtExtension } from '@veltdev/reactflow-crdt';
import '@xyflow/react/dist/style.css';

const initialNodes = [
    {
        id: '0',
        type: 'input',
        data: { label: 'Node' },
        position: { x: 0, y: 50 },
    },
];
const initialEdges: Edge[] = [];

let id = 9;
const getId = () => `${id++}`;
const nodeOrigin: [number, number] = [0.5, 0];

const AddNodeOnEdgeDrop = () => {
    const { nodes, edges, onNodesChange, onEdgesChange, onConnect } = useVeltReactFlowCrdtExtension({
        editorId: 'YOUR_EDITOR_ID',
        initialEdges,
        initialNodes,
    });

    const reactFlowWrapper = useRef(null);
    const { screenToFlowPosition } = useReactFlow();

    const onConnectEnd = useCallback(
        (event: any, connectionState: any) => {
            if (!connectionState.isValid) {
                const id = getId();
                const { clientX, clientY } = 'changedTouches' in event ? event.changedTouches[0] : event;
                const newNode = {
                    id,
                    position: screenToFlowPosition({ x: clientX, y: clientY }),
                    data: { label: `Node ${id}` },
                    origin: [0.5, 0.0],
                };

                // Add new node using CRDT-aware change handler
                onNodesChange([{ type: 'add', item: newNode }]);

                // Add new edge using CRDT-aware change handler
                const newEdge = {
                    id,
                    source: connectionState.fromNode.id,
                    target: id,
                };
                onEdgesChange([{ type: 'add', item: newEdge }]);
            }
        },
        [screenToFlowPosition, onNodesChange, onEdgesChange],
    );

    return (
        <div className="react-flow-container" ref={reactFlowWrapper} >
            <ReactFlow
                style={{ backgroundColor: "#F7F9FB" }}
                nodes={nodes}
                edges={edges}
                onNodesChange={onNodesChange}  // CRDT-synced node changes
                onEdgesChange={onEdgesChange}  // CRDT-synced edge changes
                onConnect={onConnect}  // CRDT-synced connections
                onConnectEnd={onConnectEnd}  // Custom handler for adding nodes/edges
                fitView
                fitViewOptions={{ padding: 2 }
                }
                nodeOrigin={nodeOrigin}
            >
                <Background />
            </ReactFlow>
        </div>
    );
};

function ReactFlowComponent() {
    const veltInitialized = useVeltInitState();

    if (!veltInitialized) {
        return <div>Loading...</div>;
    }

    return (
        <ReactFlowProvider>
            <AddNodeOnEdgeDrop />
        </ReactFlowProvider>
    );
}

export default ReactFlowComponent;

APIs

Custom Encryption

You can encrypt CRDT data before it’s stored in Velt by registering a custom encryption provider. For CRDT methods, input data is of type Uint8Array | number[].
async function encryptData(config: EncryptConfig<number[]>): Promise<string> {
  const encryptedData = await yourEncryptDataMethod(config.data);
  return encryptedData;
}

async function decryptData(config: DecryptConfig<string>): Promise<number[]> {
  const decryptedData = await yourDecryptDataMethod(config.data);
  return decryptedData;
}

const encryptionProvider: VeltEncryptionProvider<number[], string> = {
  encrypt: encryptData,
  decrypt: decryptData,
};

<VeltProvider
  apiKey="YOUR_API_KEY"
  encryptionProvider={encryptionProvider}
/>

See also: setEncryptionProvider() · VeltEncryptionProvider · EncryptConfig · DecryptConfig

useVeltReactFlowCrdtExtension()

Provides real-time collaborative editing on React Flow diagrams with Yjs and Velt SDK.
  • Signature: useVeltReactFlowCrdtExtension(config: VeltReactFlowCrdtExtensionConfig)
  • Params: VeltReactFlowCrdtExtensionConfig
    • editorId: Unique identifier for the diagram instance.
    • initialNodes: Initial set of nodes.
    • initialEdges: Initial set of edges.
    • debounceMs: Debounce time for update propagation (ms).
  • Returns: VeltReactFlowCrdtExtensionResponse
    • store: React Flow CRDT store instance.
    • nodes: Array of nodes in the diagram.
    • edges: Array of edges in the diagram.
    • onNodesChange: CRDT-aware handler to apply node changes.
    • onEdgesChange: CRDT-aware handler to apply edge changes.
    • onConnect: CRDT-aware handler to apply connect changes.
    • setNodes: Method to set nodes
    • setEdges: Method to set edges

onNodesChange()

CRDT-aware handler to apply node changes (add/update/remove) that sync across collaborators.
onNodesChange([{ type: 'add', item: newNode }]);

onEdgesChange()

CRDT-aware handler to apply edge changes (add/update/remove) that sync across collaborators.
onEdgesChange([{ type: 'add', item: newEdge }]);

onConnect()

CRDT-aware connect handler compatible with React Flow’s <ReactFlow onConnect={...} />.
<ReactFlow onConnect={onConnect} />

setNodes()

Imperative setter for nodes (useful for non-event updates). Synced via the store.
  • Params: Node[]
  • Returns: void
setNodes(prev => [...prev, myNode]);

setEdges()

Imperative setter for edges (useful for non-event updates). Synced via the store.
  • Params: Edge[]
  • Returns: void
setEdges(prev => [...prev, myEdge]);