Skip to main content

Introduction

Use these guides to add Yjs-based CRDT to your project with Velt. The core library supports multiple store types — pick the one that matches your data shape. Use the React Hook wrapper for the fastest integration in React apps, or the core library for other frameworks and custom implementations.

Setup

Step 1: Install Dependencies

npm install @veltdev/crdt-react @veltdev/crdt @veltdev/react

Step 2: Initialize Velt in your app

Initialize the Velt client and set the document context. This is required for the collaboration engine to work.
Wrap your app with VeltProvider and use useSetDocument to scope the collaborative session. Follow the Velt Setup Docs for full setup.
import { VeltProvider, useSetDocument } from '@veltdev/react';

function App() {
  return (
    <VeltProvider apiKey="YOUR_API_KEY">
      <YourApp />
    </VeltProvider>
  );
}

function YourApp() {
  useSetDocument('my-document-id', { documentName: 'My Document' });
  return <YourComponent />;
}

Step 3: Choose a store type

Each store type maps to a Yjs shared data structure. Pick the one that matches your use case and follow its setup guide:

Array Store

Collaborative ordered lists — todo apps, item collections, ordered datasets.

Map Store

Collaborative key-value pairs — settings, metadata, configuration objects.

Text Store

Collaborative plain text — notepads, code editors, text fields.

XML Store

Collaborative XML/tree data — outlines, structured documents, custom tree editors.

Step 4: Event subscriptions (optional)

Use the useCrdtEventCallback hook to listen for CRDT events:
import { useCrdtEventCallback } from '@veltdev/react';

function YourComponent() {
  const updateDataEvent = useCrdtEventCallback('updateData');

  useEffect(() => {
    if (updateDataEvent) {
      console.log('CRDT data updated:', updateDataEvent);
    }
  }, [updateDataEvent]);
}

Step 5: Custom encryption (optional)

Pass a custom VeltEncryptionProvider to VeltProvider to encrypt/decrypt CRDT state before it is stored on the backend.
import { VeltProvider } from '@veltdev/react';
import { VeltEncryptionProvider } from '@veltdev/types';

const encryptionProvider: VeltEncryptionProvider<number[], string> = {
  encrypt: async (config) => {
    return config.data.map((num) => num.toString()).join('__');
  },
  decrypt: async (config) => {
    return config.data.split('__').map((str) => parseInt(str, 10));
  },
};

function App() {
  return (
    <VeltProvider apiKey="YOUR_API_KEY" encryptionProvider={encryptionProvider}>
      <YourAppContent />
    </VeltProvider>
  );
}
See also: setEncryptionProvider() · VeltEncryptionProvider · EncryptConfig · DecryptConfig

APIs

React: useStore()

React hook that creates and manages a CRDT store. Handles initialization, real-time subscriptions, and cleanup automatically.
  • Signature: useStore<T>(config: UseStoreConfig<T>)
  • Params:
    • storeId: Unique document identifier.
    • type: Yjs data type: 'text', 'map', 'array', 'xml', or 'xmltext'.
    • initialValue: Initial value applied if remote state is empty.
    • debounceMs: Throttle interval (ms) for backend writes. Default: 0.
    • enablePresence: Enable presence tracking. Default: true.
    • forceResetInitialContent: If true, always reset to initialValue on init. Default: false.
    • onError: Error callback.
    • veltClient: Explicit Velt client. Falls back to VeltProvider context.
  • Returns: UseStoreReturn<T>
    • value: Current store value, reactively updated.
    • update: Replace the entire store value.
    • store: Underlying store instance for advanced use.
    • isLoading: true while the store is initializing.
    • isSynced: true when the store is connected and synced.
    • status: Connection status: 'connecting', 'connected', or 'disconnected'.
    • error: Error object if initialization failed.
    • versions: List of saved versions.
    • saveVersion: Save a named snapshot.
    • getVersions: List all saved versions.
    • getVersionById: Get a specific version by ID.
    • restoreVersion: Restore to a saved version.
    • setStateFromVersion: Apply a version’s state to the document.
import { useStore } from '@veltdev/crdt-react';

const {
  value,
  update,
  store,
  isLoading,
  isSynced,
  status,
  error,
  saveVersion,
  getVersions,
  getVersionById,
  restoreVersion,
  setStateFromVersion,
} = useStore<Item[]>({
  storeId: 'my-array-store',
  type: 'array', // can be 'array', 'map', 'text', 'xml', or 'xmltext'
  initialValue: [],
});

React: useAwareness()

React hook that wraps the Yjs Awareness instance from a store, providing reactive remote peer states and a stable setter for local awareness state.
  • Params: store: Store<any> | null — The store instance from useStore() (safe to pass null)
  • Returns: UseAwarenessReturn<T>
    • remoteStates: Awareness states of all remote peers.
    • localState: Current local awareness state.
    • setLocalState: Set or clear the local awareness state.
import { useStore, useAwareness } from '@veltdev/crdt-react';

const { store } = useStore<Item[]>({ storeId: 'my-store', type: 'array', initialValue: [] });
const { remoteStates, localState, setLocalState } = useAwareness(store);

setLocalState({
  user: { userId: 'user-1', name: 'John', color: '#ff0000' },
  cursor: { anchor: 0, head: 5 },
});

Non-React: createVeltStore()

Convenience factory that creates a Store, calls initialize(), and returns it. Returns null if initialization fails.
  • Params: StoreConfig
    • id: Unique document identifier.
    • type: Yjs data type: 'text', 'map', 'array', or 'xml'.
    • initialValue: Initial value applied if remote state is empty.
    • veltClient: Velt client instance (from initVelt()). Required for sync.
    • debounceMs: Throttle interval (ms) for backend writes. Default: 0.
    • enablePresence: Enable presence tracking. Default: true.
    • forceResetInitialContent: If true, always reset to initialValue on init. Default: false.
    • contentKey: Content key for Yjs shared types. Default: 'content'.
    • userId: User identifier for update attribution.
    • collection: Collection/namespace for document grouping.
    • logLevel: Log level: 'silent', 'error', 'warn', 'debug'. Default: 'error'.
  • Returns: Promise<Store<T> | null>
const store = await createVeltStore({
  id: 'my-array-store',
  type: 'array',
  initialValue: [{ id: '1', name: 'First item' }],
  veltClient: client,
});

Store Methods

These methods are available on the store instance (from useStore or createVeltStore):

update()

Update the store value and sync to peers.
  • Params: newValue: T
  • Returns: void
store.update('New Value');

getValue()

Get the current store value. For map stores, returns the toJSON() representation of the underlying Y.Map as a plain JavaScript object.
  • Returns: T
const value = store.getValue();

subscribe()

Subscribe to changes in the store.
  • Params: (newValue: T) => void
  • Returns: () => void (unsubscribe function)
const unsubscribe = store.subscribe((newValue) => {
  console.log('Updated value:', newValue);
});

destroy()

Clean up resources when done with the store. Automatic in React via the hook.
  • Returns: void
store.destroy();

getDoc()

Get the underlying Yjs document.
  • Returns: Y.Doc
const ydoc = store.getDoc();

getProvider()

Get the provider instance for the store.
  • Returns: Provider
const provider = store.getProvider();

getText()

Get the Y.Text instance if store type is ‘text’.
  • Returns: Y.Text | null
const ytext = store.getText();

getXml()

Get the Y.XmlFragment instance if store type is ‘xml’.
  • Returns: Y.XmlFragment | null
const yxml = store.getXml();

getAwareness()

Get the Awareness instance for cursor/presence tracking.
  • Returns: Awareness
const awareness = store.getAwareness();

Version Methods

Available on both the useStore hook return and the store instance:

saveVersion()

Save a snapshot of the current state as a named version.
  • Params: versionName: string
  • Returns: Promise<string>
const versionId = await saveVersion('Checkpoint');

getVersions()

Fetch all saved versions.
  • Returns: Promise<Version[]>
const versions = await getVersions();

getVersionById()

Fetch a specific version by ID.
  • Params: versionId: string
  • Returns: Promise<Version | null>
const version = await getVersionById('abc123');

restoreVersion()

Restore the store to a specific version using its ID.
  • Params: versionId: string
  • Returns: Promise<boolean>
await restoreVersion('abc123');

setStateFromVersion()

Restore the store state from a specific version object.
  • Params: version: Version
  • Returns: Promise<void>
await setStateFromVersion(version);

Custom Encryption

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[].
import { VeltProvider } from '@veltdev/react';
import { VeltEncryptionProvider } from '@veltdev/types';

const encryptionProvider: VeltEncryptionProvider<number[], string> = {
  encrypt: async (config) => {
    return config.data.map((num) => num.toString()).join('__');
  },
  decrypt: async (config) => {
    return config.data.split('__').map((str) => parseInt(str, 10));
  },
};

function App() {
  return (
    <VeltProvider apiKey="YOUR_API_KEY" encryptionProvider={encryptionProvider}>
      <YourAppContent />
    </VeltProvider>
  );
}
See also: setEncryptionProvider() · VeltEncryptionProvider · EncryptConfig · DecryptConfig

CRDT Event Subscriptions

on

Subscribe to CRDT events. Currently supports the updateData event which fires when CRDT data changes.
Using Hook:
import { useCrdtEventCallback } from "@veltdev/react";

export function YourComponent() {
  const crdtUpdateData = useCrdtEventCallback("updateData");

  useEffect(() => {
    console.log("[CRDT] event on data change: ", crdtUpdateData);
  }, [crdtUpdateData]);

  return <div>Your Component</div>;
}
Using API:
import { useVeltClient } from "@veltdev/react";

export function YourComponent() {
  const { client } = useVeltClient();

  useEffect(() => {
    if (!client) return;
    const crdtElement = client.getCrdtElement();
    const subscription = crdtElement.on("updateData").subscribe((eventData) => {
      console.log("[CRDT] event on data change: ", eventData);
    });
    return () => subscription.unsubscribe();
  }, [client]);

  return <div>Your Component</div>;
}

Low-Level Message APIs

These APIs provide direct access to the CRDT message stream for custom implementations.

pushMessage()

Push a raw Yjs message into the unified message stream.
const crdtElement = client.getCrdtElement();

await crdtElement.pushMessage({
  id: 'my-doc',
  data: Array.from(update),
  yjsClientId: ydoc.clientID,
  messageType: 'sync',
  source: 'tiptap',
});

onMessage()

Subscribe to incoming messages on the unified message stream. Returns an unsubscribe function.
const crdtElement = client.getCrdtElement();

const unsubscribe = crdtElement.onMessage({
  id: 'my-doc',
  callback: (msg) => {
    Y.applyUpdate(ydoc, new Uint8Array(msg.data));
  },
});

unsubscribe();

getMessages()

Retrieve historical messages from the unified message stream.
const crdtElement = client.getCrdtElement();

const messages = await crdtElement.getMessages({
  id: 'my-doc',
  afterTs: snapshot?.timestamp ?? 0,
});
for (const msg of messages) {
  Y.applyUpdate(ydoc, new Uint8Array(msg.data));
}

getSnapshot()

Retrieve the latest CRDT state snapshot for a document.
const crdtElement = client.getCrdtElement();

const snapshot = await crdtElement.getSnapshot({ id: 'my-doc' });
if (snapshot?.state) {
  Y.applyUpdate(ydoc, new Uint8Array(snapshot.state));
}

saveSnapshot()

Persist a CRDT state snapshot for a document.
const crdtElement = client.getCrdtElement();

await crdtElement.saveSnapshot({
  id: 'my-doc',
  state: Y.encodeStateAsUpdate(ydoc),
  vector: Y.encodeStateVector(ydoc),
  source: 'tiptap',
});

pruneMessages()

Delete historical messages older than a given timestamp from the message stream.
const crdtElement = client.getCrdtElement();

await crdtElement.pruneMessages({
  id: 'my-doc',
  beforeTs: Date.now() - 24 * 60 * 60 * 1000,
});

Migration Guide: v1 to v2

React

Overview

The v2 API replaces useVeltCrdtStore() with useStore(). The new hook uses storeId instead of id, adds reactive status/isSynced/error state, and supports forceResetInitialContent.

Key Changes

Aspectv1 (deprecated)v2 (current)
Entry pointuseVeltCrdtStore(config)useStore(config)
Store ID fieldidstoreId
Status trackingNot availableisLoading, isSynced, status
Error handlingNot availableonError callback + error state
Force resetNot availableforceResetInitialContent
Version managementSame methodsSame methods
CleanupAutomatic on unmountAutomatic on unmount

Step-by-Step

1. Replace the hook:
// Before (v1)
import { useVeltCrdtStore } from '@veltdev/crdt-react';
const { value, update, store } = useVeltCrdtStore<string>({ id: 'my-doc', type: 'text' });

// After (v2)
import { useStore } from '@veltdev/crdt-react';
const { value, update, store, isLoading, isSynced, status, error } = useStore<string>({
  storeId: 'my-doc',
  type: 'text',
  onError: (err) => console.error(err),
});
2. Add status monitoring (new in v2):
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;

Legacy API (v1)

The v1 useVeltCrdtStore hook is deprecated. Use the v2 useStore hook for all new React integrations. The non-React createVeltStore API remains unchanged.

useVeltCrdtStore() (deprecated)

React hook to create and sync a collaborative CRDT store. Internally delegates to useStore (v2) via a compatibility wrapper.
  • Signature: useVeltCrdtStore<T>(config)
  • Params:
    • id: Unique identifier for the store.
    • type: Type of Yjs data structure ('text' | 'array' | 'map' | 'xml').
    • initialValue: Optional initial value for the store.
    • debounceMs: Optional debounce time for update propagation (ms).
    • enablePresence: Optional boolean to enable realtime presence tracking (default: true).
  • Returns: Store properties and methods (value, update, store, versions, saveVersion, getVersions, getVersionById, restoreVersion, setStateFromVersion)
import { useVeltCrdtStore } from '@veltdev/crdt-react';

const {
  value,
  versions,
  store,
  update,
  saveVersion,
  getVersions,
  getVersionById,
  restoreVersion,
  setStateFromVersion,
} = useVeltCrdtStore<string>({
  id: 'my-collab-note',
  type: 'text',
  initialValue: 'Hello, world!',
});

Debugging

Use the Velt Chrome Extension or the window.VeltCrdtStoreMap debugging interface to inspect and monitor your CRDT stores.

window.VeltCrdtStoreMap

window.VeltCrdtStoreMap is a global debugging interface that exposes all active CRDT stores in your application. It’s automatically created and maintained by the Velt CRDT library.

get()

Get a store by its ID.
  • Params:
    • id (optional): Store ID. If omitted, returns the first registered store.
  • Returns: VeltCrdtStore | undefined
    • Store object methods:
      • getValue(): Get the current value from the store
      • subscribe(callback: (value: any) => void): Subscribe to changes. Returns an unsubscribe function.
// Get a specific store
const store = window.VeltCrdtStoreMap.get('my-store-id');
if (store) {
  console.log('Current value:', store.getValue());
}

// Get the first registered store
const firstStore = window.VeltCrdtStoreMap.get();