Skip to main content
  • This is currently only compatible with setDocuments method.
  • Ensure that the data providers are set prior to calling identify method.
  • The data provider methods must return the correct status code (e.g. 200 for success, 500 for errors) and success boolean in the response object. This ensures proper error handling and retries.

Overview

Velt supports self-hosting your reactions and related data:
  • Reactions can be stored on your own infrastructure, with only necessary identifiers on Velt servers.
  • Velt Components automatically hydrate reaction data in the frontend by fetching from your configured data provider.
  • This gives you full control over reaction data while maintaining all Velt collaboration features.
  • This automatically also ensures that the in-app notifications content related to reactions is not stored on Velt servers. The content is generated using the reactions data in the frontend.

How does it work?

When users add or remove reactions:
  1. The SDK uses your configured ReactionAnnotationDataProvider to handle storage
  2. Your data provider implements three key methods:
    • get: Fetches reactions from your database
    • save: Stores reactions and returns success/error
    • delete: Removes reactions from your database
The process works as follows: When a reaction operation occurs:
  1. The SDK first attempts to save/delete the reaction on your database
  2. If successful:
    • The SDK updates Velt’s servers with minimal metadata
    • The PartialReactionAnnotation object is updated with the reaction details including emoji, user, and metadata
    • When the reaction is saved, this information is stored on your end
    • Velt servers only store necessary identifiers, not the actual reaction content
  3. If the operation fails, no changes are made to Velt’s servers and the operation is retried if you have configured retries.
You can configure retries, timeouts, etc. for the data provider.

Implementation Approaches

You can implement reaction self-hosting using either of these approaches:
  1. Config-Based: Provide endpoint URLs and let the SDK handle HTTP requests
  2. Custom Methods: Implement get, save, and delete methods yourself
Both approaches are fully backward compatible and can be used together.

Config-Based Approach

Instead of implementing custom methods, you can configure endpoints directly and let the SDK handle HTTP requests.

getConfig

Config-based endpoint for fetching reactions. The SDK automatically makes HTTP POST requests with the request body.
const reactionResolverConfig = {
  getConfig: {
    url: '/api/velt/reactions/get',
    headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
  }
};

const reactionDataProvider = {
  config: reactionResolverConfig
};

<VeltProvider
  apiKey='YOUR_API_KEY'
  dataProviders={{ reaction: reactionDataProvider }}
>
</VeltProvider>

saveConfig

Config-based endpoint for saving reactions. The SDK automatically makes HTTP POST requests with the request body.
const reactionResolverConfig = {
  saveConfig: {
    url: '/api/velt/reactions/save',
    headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
  }
};

const reactionDataProvider = {
  config: reactionResolverConfig
};

<VeltProvider
  apiKey='YOUR_API_KEY'
  dataProviders={{ reaction: reactionDataProvider }}
>
</VeltProvider>

deleteConfig

Config-based endpoint for deleting reactions. The SDK automatically makes HTTP POST requests with the request body.
const reactionResolverConfig = {
  deleteConfig: {
    url: '/api/velt/reactions/delete',
    headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
  }
};

const reactionDataProvider = {
  config: reactionResolverConfig
};

<VeltProvider
  apiKey='YOUR_API_KEY'
  dataProviders={{ reaction: reactionDataProvider }}
>
</VeltProvider>

Config-Based Complete Example

const reactionResolverConfig = {
  getConfig: {
    url: '/api/velt/reactions/get',
    headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
  },
  saveConfig: {
    url: '/api/velt/reactions/save',
    headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
  },
  deleteConfig: {
    url: '/api/velt/reactions/delete',
    headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
  },
  resolveTimeout: 2000,
  getRetryConfig: { retryCount: 3, retryDelay: 2000 },
  saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
  deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};

const reactionDataProvider = {
  config: reactionResolverConfig
};

<VeltProvider
  apiKey='YOUR_API_KEY'
  dataProviders={{ reaction: reactionDataProvider }}
>
</VeltProvider>

Custom Methods Approach

Implement custom methods to handle data operations yourself.

get

Method to fetch reactions from your database. On error we will retry.
const fetchReactionsFromDB = async (request) => {
  const response = await fetch('/api/velt/reactions/get', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(request)
  });
  return await response.json();
};

const reactionDataProvider = {
  get: fetchReactionsFromDB,
};

<VeltProvider
  apiKey='YOUR_API_KEY'
  dataProviders={{ reaction: reactionDataProvider }}
>
</VeltProvider>

save

Save reactions to your database. Return a success or error response. On error we will retry.
const saveReactionsToDB = async (request) => {
  const response = await fetch('/api/velt/reactions/save', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(request)
  });
  return await response.json();
};

const reactionDataProvider = {
  save: saveReactionsToDB,
};

<VeltProvider
  apiKey='YOUR_API_KEY'
  dataProviders={{ reaction: reactionDataProvider }}
>
</VeltProvider>

delete

Delete reactions from your database. Return a success or error response. On error we will retry.
const deleteReactionsFromDB = async (request) => {
  const response = await fetch('/api/velt/reactions/delete', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(request)
  });
  return await response.json();
};

const reactionDataProvider = {
  delete: deleteReactionsFromDB,
};

<VeltProvider
  apiKey='YOUR_API_KEY'
  dataProviders={{ reaction: reactionDataProvider }}
>
</VeltProvider>

config

Configuration for the reaction data provider.
  • Type: ResolverConfig. Relevant properties:
    • resolveTimeout: Timeout duration (in milliseconds) for resolver operations
    • getRetryConfig: RetryConfig. Configure retry behavior for get operations.
    • saveRetryConfig: RetryConfig. Configure retry behavior for save operations.
    • deleteRetryConfig: RetryConfig. Configure retry behavior for delete operations.
const reactionResolverConfig = {
  resolveTimeout: 2000,
  getRetryConfig: { retryCount: 3, retryDelay: 2000 },
  saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
  deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};

Custom Methods Complete Example

const fetchReactionsFromDB = async (request) => {
  const response = await fetch('/api/velt/reactions/get', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(request)
  });
  return await response.json();
};

const saveReactionsToDB = async (request) => {
  const response = await fetch('/api/velt/reactions/save', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(request)
  });
  return await response.json();
};

const deleteReactionsFromDB = async (request) => {
  const response = await fetch('/api/velt/reactions/delete', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(request)
  });
  return await response.json();
};

const reactionResolverConfig = {
  resolveTimeout: 2000,
  getRetryConfig: { retryCount: 3, retryDelay: 2000 },
  saveRetryConfig: { retryCount: 3, retryDelay: 2000 },
  deleteRetryConfig: { retryCount: 3, retryDelay: 2000 }
};

const reactionDataProvider = {
  get: fetchReactionsFromDB,
  save: saveReactionsToDB,
  delete: deleteReactionsFromDB,
  config: reactionResolverConfig
};

<VeltProvider
  apiKey='YOUR_API_KEY'
  dataProviders={{ reaction: reactionDataProvider }}
>
</VeltProvider>

Sample Data

{
    "annotationId": "REACTION_ANNOTATION_ID",
    "metadata": {
        "apiKey": "API_KEY",
        "documentId": "DOCUMENT_ID",
        "organizationId": "ORGANIZATION_ID"
    },
    "icon": "HEART_FACE"
}
The reaction annotation on your database stores the actual reaction icon/emoji content.

Debugging

You can subscribe to dataProvider events to monitor and debug get, save, and delete operations. The event includes a moduleName field that identifies which module triggered the resolver call, helping you trace data provider requests. Type: ReactionResolverModuleName
import { useVeltClient } from '@veltdev/react';

const { client } = useVeltClient();

useEffect(() => {
  if (!client) return;

  const subscription = client.on('dataProvider').subscribe((event) => {
    console.log('Data Provider Event:', event);
    console.log('Module Name:', event.moduleName);
  });

  return () => subscription?.unsubscribe();
}, [client]);

API Reference

setDataProviders()

Configure data providers for self-hosting reaction data.