Introduction
Use these guides to add Yjs-based CRDT to your project with Velt. Pick 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
React / Next.js
Other Frameworks
npm:npm install @veltdev/crdt-react @veltdev/crdt @veltdev/react
yarn:yarn add @veltdev/crdt-react @veltdev/crdt @veltdev/react
npm:npm install @veltdev/crdt @veltdev/client
yarn:yarn add @veltdev/crdt @veltdev/client
Step 2: Initialize Velt in your app
Initialize the Velt client by following the Velt Setup Docs. This is required for the collaboration engine to work.
Step 3: Initialize a CRDT store
React / Next.js
Other Frameworks
import { useVeltCrdtStore } from '@veltdev/crdt-react';
function Component() {
const { store } = useVeltCrdtStore<string>({
id: 'my-collab-note',
type: 'text',
initialValue: 'Hello, world!',
});
return null;
}
import { createVeltStore } from '@veltdev/crdt';
import { initVelt } from '@veltdev/client';
const veltClient = await initVelt('YOUR_API_KEY');
const store = await createVeltStore<string>({
id: 'my-document',
type: 'text', // 'array' | 'map' | 'text' | 'xml'
initialValue: 'Hello, world!',
veltClient,
});
Step 4: Set or update the store value
Set or update local changes to the store.
React / Next.js
Other Frameworks
import { useVeltCrdtStore } from '@veltdev/crdt-react';
function Component() {
const { update } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
const onChange = (e) => update(e.target.value);
return <input onChange={onChange} />;
}
store.update('Hello, collaborative world!');
Step 5: Listen for changes
Listen and subscribe for updates from local and remote peers.
React / Next.js
Other Frameworks
import { useEffect } from 'react';
import { useVeltCrdtStore } from '@veltdev/crdt-react';
function Component() {
const { value } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
useEffect(() => {
console.log('Updated value:', value);
}, [value]);
return null;
}
const unsubscribe = store.subscribe((newValue) => {
console.log('Updated value:', newValue);
});
Step 6: Save and restore versions
Create checkpoints and roll back when needed.
React / Next.js
Other Frameworks
import { useVeltCrdtStore } from '@veltdev/crdt-react';
function Component() {
const { saveVersion, getVersions, getVersionById, setStateFromVersion } =
useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
async function onSave() {
const versionId = await saveVersion('Checkpoint');
console.log('Saved version:', versionId);
}
async function onRestoreLatest() {
const versions = await getVersions();
if (versions.length === 0) return;
const latest = versions[0];
await setStateFromVersion(latest);
}
return (
<div>
<button onClick={onSave}>Save Version</button>
<button onClick={onRestoreLatest}>Restore Latest</button>
</div>
);
}
const versionId = await store.saveVersion('Initial checkpoint');
const versions = await store.getVersions();
const fetched = await store.getVersionById(versionId);
if (fetched) {
await store.setStateFromVersion(fetched);
}
APIs
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[].
React / Next.js
Other Frameworks
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}
/>
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,
};
client.setEncryptionProvider(encryptionProvider);
See also: setEncryptionProvider()
· VeltEncryptionProvider
· EncryptConfig
· DecryptConfig
Core
Initialization
React / Next.js
Other Frameworks
React hook to create and sync a collaborative CRDT store.
- Params: useveltcrdtstore
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
const {
value,
versions,
store,
update,
saveVersion,
getVersions,
getVersionById,
restoreVersion,
setStateFromVersion,
} = useVeltCrdtStore<string>({
id: 'my-collab-note',
type: 'text',
initialValue: 'Hello, world!',
debounceMs: 100,
enablePresence: true,
});
Create and initialize a collaborative CRDT store.
- Params: StoreConfig
id: Unique identifier for the store.
type: Type of Yjs data structure ('text' | 'array' | 'map' | 'xml').
initialValue: Optional initial value for the store.
veltClient: Velt client instance (from initVelt()).
debounceMs: Optional debounce time for update propagation (ms).
enablePresence: Optional boolean to enable realtime presence tracking (default: true).
- Returns:
Promise<Store<T> | null>
const store = await createVeltStore<string>({
id: 'my-document',
type: 'text',
initialValue: 'Hello, world!',
veltClient,
debounceMs: 100,
enablePresence: true,
});
Update the store value and sync to peers.
- Params:
newValue: T
- Returns:
void
const { update } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
update('New value');
Access the current store value.
Get one-time current value
React / Next.js
Other Frameworks
valueReactive property that updates automatically when the store changes.const { value } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
console.log(value);
getValue()Method to retrieve the current store value.const value = store.getValue();
console.log(value);
Subscribe to value changes
Listen for updates from local and remote peers.
React / Next.js
Other Frameworks
Use useEffect with the reactive value property for automatic updates, or use store.subscribe() for manual subscriptions.const { store, value } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
// Option 1: Use reactive value (recommended)
useEffect(() => {
console.log('Value changed:', value);
}, [value]);
// Option 2: Manual subscription
useEffect(() => {
if (!store) return;
const subscription = store.subscribe((newValue) => {
console.log('Updated value:', newValue);
});
return () => subscription?.();
}, [store]);
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);
});
// Later, to stop listening:
unsubscribe();
Access the underlying Velt Store instance for advanced operations.
React / Next.js
Other Frameworks
The underlying store instance returned by the hook.const { store } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
console.log(store);
The store instance is returned directly from createVeltStore().const store = await createVeltStore<string>({
id: 'my-document',
type: 'text',
veltClient,
});
console.log(store);
Clean up resources when done with the store.
React / Next.js
Other Frameworks
Cleanup is handled automatically by the hook when the component unmounts. No manual cleanup is required.// Cleanup happens automatically when component unmounts
const { store } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
Manually destroy the store to clean up resources and listeners.// When done with the store
store.destroy();
Subscribe to CRDT events. Currently supports the updateData event which fires when CRDT data changes.
React / Next.js
Other Frameworks
Using Hook:import { useCrdtEventCallback } from "@veltdev/react";
import { useEffect } from "react";
export function YourComponent() {
// Hook: Subscribe to updateData event
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";
import { useEffect } from "react";
export function YourComponent() {
const { client } = useVeltClient();
useEffect(() => {
if (!client) return;
const crdtElement = client.getCrdtElement();
// Subscribe to updateData event
const subscription = crdtElement.on("updateData").subscribe((eventData) => {
console.log("[CRDT] event on data change: ", eventData);
});
return () => subscription.unsubscribe();
}, [client]);
return <div>Your Component</div>;
}
<script>
// Get CRDT element
const crdtElement = Velt.getCrdtElement();
// Subscribe to updateData event
crdtElement.on("updateData").subscribe((eventData) => {
console.log("[CRDT] event on data change: ", eventData);
// eventData structure:
// {
// methodName: string,
// uniqueId: string,
// timestamp: number,
// source: string,
// payload: {
// id: string,
// data: unknown,
// lastUpdatedBy: string,
// sessionId: string | null,
// lastUpdate: string
// }
// }
});
</script>
Versions
Save a snapshot of the current state as a named version.
- Params:
versionName: string
- Returns:
Promise<string>
React / Next.js
Other Frameworks
const { saveVersion } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
await saveVersion('Checkpoint');
const versionId = await store.saveVersion('Initial checkpoint');
Fetch all saved versions.
- Returns:
Promise<Version[]>
React / Next.js
Other Frameworks
Reactive property:const { versions } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
console.log(versions);
Async method:const { getVersions } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
const versions = await getVersions();
const versions = await store.getVersions();
Fetch a specific version by ID.
- Params:
versionId: string
- Returns:
Promise<Version | null>
React / Next.js
Other Frameworks
const { getVersionById } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
const version = await getVersionById('abc123');
const version = await store.getVersionById('abc123');
Restore the store state from a specific version object.
- Params:
version: Version
- Returns:
Promise<void>
React / Next.js
Other Frameworks
const { setStateFromVersion } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
await setStateFromVersion(version);
await store.setStateFromVersion(version);
Restore the store to a specific version using its ID.
React / Next.js
Other Frameworks
Convenience method that fetches and restores a version in one call.
- Params:
versionId: string
- Returns:
Promise<boolean>
const { restoreVersion } = useVeltCrdtStore<string>({ id: 'my-collab-note', type: 'text' });
await restoreVersion('abc123');
Combine getVersionById() and setStateFromVersion() to achieve the same result.const version = await store.getVersionById('abc123');
if (version) {
await store.setStateFromVersion(version);
}
Yjs
Get the underlying Yjs document.
const ydoc = store.getDoc();
Get the provider instance for the store.
const provider = store.getProvider();
Get the Y.Text instance if store type is ‘text’.
const ytext = store.getText();
Get the Y.XmlFragment instance if store type is ‘xml’.
- Returns:
Y.XmlFragment | null
const yxml = store.getXml();
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, allowing you to inspect, monitor, and debug CRDT stores from the browser console or developer tools.
Get a store by its ID.
- Params:
id (optional): Store ID. If omitted, returns the first registered store.
- Returns:
VeltCrdtStore | undefined - Store instance or undefined if not found
- Store object methods:
getValue(): Get the current value from the store
subscribe(callback: (value: any) => void): Subscribe to changes in the store. Returns an unsubscribe function.
Basic Usage
Inspect Store
// 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();
// Get store and inspect
const store = window.VeltCrdtStoreMap.get('editor-123');
if (store) {
// Get current value
console.log('Current value:', store.getValue());
// Subscribe to changes
store.subscribe((value) => {
console.log('Value updated:', value);
});
// Log value every second
setInterval(() => {
console.log('Current value:', store.getValue());
}, 1000);
}
Get all registered stores as an object.
- Returns:
{ [id: string]: VeltCrdtStore } - Object mapping store IDs to store instances
- Each store object provides:
getValue(): Get the current value from the store
subscribe(callback: (value: any) => void): Subscribe to changes in the store. Returns an unsubscribe function.
Basic Usage
Find Stores
Monitor All Stores
// Get all stores
const allStores = window.VeltCrdtStoreMap.getAll();
console.log('Total stores:', Object.keys(allStores).length);
// Iterate through all stores
Object.entries(allStores).forEach(([id, store]) => {
console.log(`Store ${id}:`, store.getValue());
});
// Find all stores
const allStores = window.VeltCrdtStoreMap.getAll();
// Find stores by pattern
const editorStores = Object.keys(allStores).filter(id =>
id.startsWith('editor-')
);
console.log('Editor stores:', editorStores);
// Get specific subset
const documentStores = Object.keys(allStores).filter(id =>
id.includes('document')
);
// Monitor all stores for changes
Object.keys(window.VeltCrdtStoreMap.stores).forEach(storeId => {
const store = window.VeltCrdtStoreMap.get(storeId);
store.subscribe((value) => {
console.log(`Store "${storeId}" changed:`, value);
});
});
Events
The library dispatches custom events when stores are registered or unregistered.
veltCrdtStoreRegister
Fired when a new store is registered.
- Event Detail:
{ id: string } - Store ID
window.addEventListener('veltCrdtStoreRegister', (event) => {
console.log('Store registered:', event.detail.id);
const store = window.VeltCrdtStoreMap.get(event.detail.id);
// Do something with the new store
});
veltCrdtStoreUnregister
Fired when a store is unregistered (destroyed).
- Event Detail:
{ id: string } - Store ID
window.addEventListener('veltCrdtStoreUnregister', (event) => {
console.log('Store unregistered:', event.detail.id);
});
Backend Data Retrieval
You can retrieve CRDT editor data from the backend using the REST API. This is useful for server-side processing, backups, or integrations.
Use the Get CRDT Data REST API endpoint to fetch CRDT data on demand:
- Retrieve data for a specific editor by passing the
editorId
- Get all CRDT data for a document by omitting the
editorId
This complements the SDK methods for frontend usage and provides backend access to your collaborative data.
Webhooks
CRDT supports webhooks for data change events. Webhooks are disabled by default. When enabled, the default debounce time is 5 seconds.
For more information on setting up webhooks, see:
Enabling webhooks involves two steps:
- Enable webhooks in the Velt Console
- Enable webhooks in the frontend using the
enableWebhook() method
Step 1: Enable in Velt Console
Configure your webhook endpoint in the Velt Console.
Step 2: Enable in Frontend
Call the enableWebhook() method to enable webhook notifications. You can also optionally configure the debounce time.
React / Next.js
Other Frameworks
Using Hook:import { useCrdtUtils } from "@veltdev/react";
import { useEffect } from "react";
export function YourComponent() {
const crdtUtils = useCrdtUtils();
useEffect(() => {
if (crdtUtils) {
// Enable webhook
crdtUtils.enableWebhook();
// Optional: Change webhook debounce time (minimum 5 seconds)
crdtUtils.setWebhookDebounceTime(10 * 1000); // 10 seconds
}
}, [crdtUtils]);
return <div>Your Component</div>;
}
Using API:import { useVeltClient } from "@veltdev/react";
import { useEffect } from "react";
export function YourComponent() {
const { client } = useVeltClient();
useEffect(() => {
if (!client) return;
const crdtElement = client.getCrdtElement();
// Enable webhook
crdtElement.enableWebhook();
// Optional: Change webhook debounce time (minimum 5 seconds)
crdtElement.setWebhookDebounceTime(10 * 1000); // 10 seconds
}, [client]);
return <div>Your Component</div>;
}
<script>
// Get CRDT element
const crdtElement = Velt.getCrdtElement();
// Enable webhook
crdtElement.enableWebhook();
// Optional: Change webhook debounce time (minimum 5 seconds)
crdtElement.setWebhookDebounceTime(10 * 1000); // 10 seconds
</script>
Webhook Methods
enableWebhook()
Enable webhook notifications for CRDT data changes.
- Params:
void
- Returns:
void
disableWebhook()
Disable webhook notifications for CRDT data changes.
- Params:
void
- Returns:
void
setWebhookDebounceTime()
Set the debounce time for webhook notifications.
- Params:
debounceTime: number (in milliseconds, minimum 5000ms)
- Returns:
void
The minimum debounce time is 5 seconds (5000ms). Setting a value lower than this will use 5 seconds as the default.