> ## Documentation Index
> Fetch the complete documentation index at: https://velt.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Users

> Self-host your users' PII while using Velt's collaboration features. Keep sensitive user data on your infrastructure with only user IDs stored on Velt servers.

> [!WARNING]
>
> Ensure that the data providers are set prior to calling `identify` method.

# Overview

Velt supports self-hosting your users' personally identifiable information (PII):

* Only the userId is stored on Velt servers, keeping sensitive user metadata on your infrastructure
* Velt Components automatically hydrate user details in the frontend by fetching from your configured data provider
* This gives you full control over user data while maintaining all Velt functionality

## How does it work?

* When the SDK is initialized, it will call the [`UserDataProvider`](https://velt.dev/docs/api-reference/sdk/models/data-models#userdataprovider) you configure with the list of userIds that it needs to fetch for the currently set user, organization, document, etc.
* The [`UserDataProvider`](https://velt.dev/docs/api-reference/sdk/models/data-models#userdataprovider) takes in a list of userIds and returns a Record object with the userIds as keys and the user data as values.

# Implementation Approaches

You can implement user self-hosting using either of these approaches:

1. **Endpoint based**: Provide an endpoint URL and let the SDK handle HTTP requests
2. **Function based**: Implement the `get` method yourself

Both approaches are fully backward compatible and can be used together.

| Feature | Function based | Endpoint based |
| - | - | - |
| **Best For** | Complex setups requiring middleware logic, dynamic headers, or transformation before sending | Standard REST APIs where you just need to pass the request "as-is" to the backend |
| **Implementation** | You write the `fetch()` or `axios` code | You provide the endpoint `url`, static or async `headers`, and optional `credentials` |
| **Flexibility** | High | Medium |
| **Speed** | Medium | High |

## Endpoint based DataProvider

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

> [!NOTE]
>
> `headers` may also be an async function resolved per request, and `credentials` (`'include' | 'same-origin' | 'omit'`) enables cookie/session auth — see [Async headers and credentials](https://velt.dev/docs/self-hosting/partial/overview.md#async-headers-and-credentials) for details.

### getConfig

Config-based endpoint for fetching users. The SDK automatically makes HTTP POST requests with the request body.

* Type: [`ResolverEndpointConfig`](https://velt.dev/docs/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request body format: [`GetUserResolverRequest`](https://velt.dev/docs/api-reference/sdk/models/data-models#getuserresolverrequest) - `{ organizationId: string, userIds: string[] }`
* Response format: [`ResolverResponse<Record<string, User>>`](https://velt.dev/docs/api-reference/sdk/models/data-models#resolverresponse)

> [!NOTE]
>
> The config-based approach uses a different request format than the custom methods approach. It passes `{ organizationId, userIds }` instead of just `userIds`.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const userConfig = {
>   getConfig: {
>     url: 'https://your-backend.com/api/velt/users/get',
>     headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
>   },
>   resolveUsersConfig: {
>     organization: false,
>     folder: false,
>     document: true
>   }
> };
>
> const userDataProvider = {
>   config: userConfig
> };
>
> <VeltProvider
>   apiKey='YOUR_API_KEY'
>   dataProviders={{ user: userDataProvider }}
> >
> </VeltProvider>
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> const userConfig = {
>   getConfig: {
>     url: 'https://your-backend.com/api/velt/users/get',
>     headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
>   },
>   resolveUsersConfig: {
>     organization: false,
>     folder: false,
>     document: true
>   }
> };
>
> const userDataProvider = {
>   config: userConfig
> };
>
> Velt.setDataProviders({ user: userDataProvider });
> ```

### Endpoint based Complete Example

> [tab] React / Next.js
>
> ```jsx theme={null}
> const userConfig = {
>   getConfig: {
>     url: 'https://your-backend.com/api/velt/users/get',
>     headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
>   },
>   resolveUsersConfig: {
>     organization: false,
>     folder: false,
>     document: true
>   }
> };
>
> const userDataProvider = {
>   config: userConfig
> };
>
> <VeltProvider
>   apiKey='YOUR_API_KEY'
>   dataProviders={{ user: userDataProvider }}
> >
> </VeltProvider>
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> const userConfig = {
>   getConfig: {
>     url: 'https://your-backend.com/api/velt/users/get',
>     headers: { 'Authorization': 'Bearer YOUR_TOKEN' }
>   },
>   resolveUsersConfig: {
>     organization: false,
>     folder: false,
>     document: true
>   }
> };
>
> const userDataProvider = {
>   config: userConfig
> };
>
> Velt.setDataProviders({ user: userDataProvider });
> ```

## Function based DataProvider

Here are the methods that you need to implement on the data provider:

### get

Method to fetch users from your database.

* Param: `string[]`: Array of userIds to fetch
* Return: `Promise<Record<string, User>>` or [`Promise<ResolverResponse<Record<string, User>>>`](https://velt.dev/docs/api-reference/sdk/models/data-models#resolverresponse)

> [!NOTE]
>
> Both response formats are supported for backward compatibility:
>
> * **Old format**: `Record<string, User>` - Returns user data directly
> * **New format**: [`ResolverResponse<Record<string, User>>`](https://velt.dev/docs/api-reference/sdk/models/data-models#resolverresponse) - Returns `{ data, success, statusCode, message?, timestamp? }`

> [tab] Frontend Example
>
> ```jsx React / Next.js theme={null}
> const fetchUsersFromDB = async (userIds) => {
>   const response = await fetch('/api/velt/users/get', {
>     method: 'POST',
>     headers: { 'Content-Type': 'application/json' },
>     body: JSON.stringify({ userIds })
>   });
>   return await response.json();
> };
>
> const userDataProvider = {
>   get: fetchUsersFromDB,
> };
>
> <VeltProvider
>   apiKey='YOUR_API_KEY'
>   dataProviders={{ user: userDataProvider }}
> >
> </VeltProvider>
> ```
>
> ```js Other Frameworks theme={null}
> const fetchUsersFromDB = async (userIds) => {
>   const response = await fetch('/api/velt/users/get', {
>     method: 'POST',
>     headers: { 'Content-Type': 'application/json' },
>     body: JSON.stringify({ userIds })
>   });
>   return await response.json();
> };
>
> const userDataProvider = {
>   get: fetchUsersFromDB,
> };
>
> Velt.setDataProviders({ user: userDataProvider });
> ```

> [tab] Backend Endpoint Example (MongoDB)
>
> ```javascript theme={null}
> const { userIds } = req.body;
>
> const users = await collection.find({
>   userId: { $in: userIds }
> }).toArray();
>
> // Convert to Record<userId, User>
> const result = {};
> for (const user of users) {
>   result[user.userId] = user;
> }
>
> // Return response in required format
> res.json(result);
> ```

> [tab] Backend Endpoint Example (PostgreSQL)
>
> ```javascript theme={null}
> const { userIds } = req.body;
>
> const { rows } = await client.query(
>   'SELECT user_id, name, email, photo_url FROM users WHERE user_id = ANY($1)',
>   [userIds]
> );
>
> // Convert to Record<userId, User>
> const result = {};
> for (const row of rows) {
>   result[row.user_id] = {
>     userId: row.user_id,
>     name: row.name,
>     email: row.email,
>     photoUrl: row.photo_url
>   };
> }
>
> // Return response in required format
> res.json(result);
> ```

### config

Configuration for the user data provider.

* Type: [`ResolverConfig`](https://velt.dev/docs/api-reference/sdk/models/data-models#resolverconfig). Relevant properties:
  * `resolveUsersConfig`: [`ResolveUsersConfig`](https://velt.dev/docs/api-reference/sdk/models/data-models#resolveusersconfig). Controls whether Velt issues user-resolver requests during initialization to fetch contact lists at the organization, folder, and document levels. Use this to optimize performance in environments with a large number of users by preventing unnecessary user-data fetches. You can selectively disable user-resolver requests for specific scopes and instead provide your own user data via the [custom autocomplete feature](https://velt.dev/docs/async-collaboration/comments/customize-behavior.md#customautocompletesearch).
    * `organization`: boolean - Enable/disable user requests for organization users (default: true)
    * `document`: boolean - Enable/disable user requests for document users (default: true)
    * `folder`: boolean - Enable/disable user requests for folder users (default: true)

```jsx theme={null}
const userConfig = {
  resolveUsersConfig: {
    organization: false,
    folder: false,
    document: true
  }
};
```

### Function based Complete Example

> [tab] React / Next.js
>
> ```jsx theme={null}
> const fetchUsersFromDB = async (userIds) => {
>   const response = await fetch('/api/velt/users/get', {
>     method: 'POST',
>     headers: { 'Content-Type': 'application/json' },
>     body: JSON.stringify({ userIds })
>   });
>   return await response.json();
> };
>
> const userDataProvider = {
>   get: fetchUsersFromDB,
>   config: {
>     resolveUsersConfig: {
>       organization: false,
>       folder: false,
>       document: true
>     }
>   }
> };
>
> <VeltProvider
>   apiKey='YOUR_API_KEY'
>   dataProviders={{ user: userDataProvider }}
> >
> </VeltProvider>
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> const fetchUsersFromDB = async (userIds) => {
>   const response = await fetch('/api/velt/users/get', {
>     method: 'POST',
>     headers: { 'Content-Type': 'application/json' },
>     body: JSON.stringify({ userIds })
>   });
>   return await response.json();
> };
>
> const userDataProvider = {
>   get: fetchUsersFromDB,
>   config: {
>     resolveUsersConfig: {
>       organization: false,
>       folder: false,
>       document: true
>     }
>   }
> };
>
> Velt.setDataProviders({ user: userDataProvider });
> ```

# Anonymous User Resolution

When a user who is not part of the contact list is tagged by email in a comment, they won't have a `userId`. The [`AnonymousUserDataProvider`](https://velt.dev/docs/api-reference/sdk/models/data-models#anonymoususerdataprovider) resolves these email → `userId` mappings automatically. When a comment is saved and a tagged contact or `to` recipient has an email but no `userId`, the SDK calls the registered provider to look up the `userId`, backfills it into the comment data before persisting, and optionally writes a stub user record to the organization database.

* Type: [`AnonymousUserDataProvider`](https://velt.dev/docs/api-reference/sdk/models/data-models#anonymoususerdataprovider)
  * `resolveUserIdsByEmail`: Callback that maps email addresses to user IDs. Called automatically at comment save time.
    * Param: [`ResolveUserIdsByEmailRequest`](https://velt.dev/docs/api-reference/sdk/models/data-models#resolveuseridsbyemailrequest) - `{ organizationId: string, documentId?: string, folderId?: string, emails: string[] }`
    * Return: [`Promise<ResolverResponse<Record<string, string>>>`](https://velt.dev/docs/api-reference/sdk/models/data-models#resolverresponse) - `{ statusCode, success, data: { [email]: userId } }`
  * `config`: Optional configuration for timeout and retry behavior.
    * Type: [`AnonymousUserDataProviderConfig`](https://velt.dev/docs/api-reference/sdk/models/data-models#anonymoususerdataproviderconfig)
    * `resolveTimeout`: `number` - Timeout in milliseconds for the resolve call
    * `getRetryConfig`: [`RetryConfig`](https://velt.dev/docs/api-reference/sdk/models/data-models#retryconfig) - `{ retryCount, retryDelay }`

Key behaviors:

* Results are cached in memory for the session; emails already resolved are not re-fetched.
* On timeout or error, the provider returns whatever was cached and proceeds without the unresolved user IDs.
* If no [`UserDataProvider`](https://velt.dev/docs/api-reference/sdk/models/data-models#userdataprovider) is configured, resolved user IDs are automatically backfilled to the organization database as stub user records (`{ userId, name: email, email }`).
* If a `UserDataProvider` is already configured, the org DB backfill is skipped.

> [tab] React / Next.js
>
> ```jsx theme={null}
> // Option 1: via VeltProvider
> const anonymousUserDataProvider = {
>   resolveUserIdsByEmail: async (request) => {
>     const map = await yourBackend.lookupUserIds(request.emails);
>     return { statusCode: 200, success: true, data: map };
>   },
>   config: {
>     resolveTimeout: 5000,
>     getRetryConfig: { retryCount: 2, retryDelay: 300 },
>   },
> };
>
> <VeltProvider
>   apiKey='YOUR_API_KEY'
>   dataProviders={{ anonymousUser: anonymousUserDataProvider }}
> >
> </VeltProvider>
>
> // Option 2: standalone method
> client.setAnonymousUserDataProvider({
>   resolveUserIdsByEmail: async (request) => {
>     const map = await yourBackend.lookupUserIds(request.emails);
>     return { statusCode: 200, success: true, data: map };
>   },
>   config: {
>     resolveTimeout: 5000,
>     getRetryConfig: { retryCount: 2, retryDelay: 300 },
>   },
> });
>
> // Option 3: via setDataProviders
> client.setDataProviders({
>   anonymousUser: {
>     resolveUserIdsByEmail: async (request) => { /* ... */ },
>   },
> });
> ```

> [tab] Other Frameworks
>
> ```html theme={null}
> <script>
> // Option 1: standalone method
> Velt.setAnonymousUserDataProvider({
>   resolveUserIdsByEmail: async (request) => {
>     const map = await yourBackend.lookupUserIds(request.emails);
>     return { statusCode: 200, success: true, data: map };
>   },
>   config: {
>     resolveTimeout: 5000,
>     getRetryConfig: { retryCount: 2, retryDelay: 300 },
>   },
> });
>
> // Option 2: via setDataProviders
> Velt.setDataProviders({
>   anonymousUser: {
>     resolveUserIdsByEmail: async (request) => { /* ... */ },
>   },
> });
> </script>
> ```

# Sample Data

> [tab] Stored on your database
>
> ```json theme={null}
> {
>     "USER_ID_1": {
>         "userId": "USER_ID_1",
>         "name": "John Doe",
>         "email": "john.doe@example.com",
>         "photoUrl": "https://example.com/photos/john.jpg",
>         "organizationId": "ORG_ID",
>         "role": "admin"
>     },
>     "USER_ID_2": {
>         "userId": "USER_ID_2",
>         "name": "Jane Smith",
>         "email": "jane.smith@example.com",
>         "photoUrl": "https://example.com/photos/jane.jpg",
>         "organizationId": "ORG_ID",
>         "role": "member"
>     }
> }
> ```

> [tab] Stored on Velt servers
>
> ```json theme={null}
> {
>     "userId": "USER_ID_1"
> }
> ```
>
> Only the userId is stored on Velt servers. All other user metadata (name, email, photoUrl, etc.) remains on your infrastructure.

# Debugging

You can subscribe to `dataProvider` events to monitor and debug get operations. The event includes a `moduleName` field that identifies which module triggered the resolver call, helping you trace data provider requests.

You can also use the [Velt Chrome DevTools extension](https://chromewebstore.google.com/detail/velt-devtools/nfldoicbagllmegffdapcnohakpamlnl) to inspect and debug your Velt implementation.

Type: [`UserResolverModuleName`](https://velt.dev/docs/api-reference/sdk/models/data-models#userresolvermodulename)

> [tab] React / Next.js
>
> ```jsx theme={null}
> 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]);
> ```

> [tab] Other Frameworks
>
> ```javascript theme={null}
> const subscription = Velt.on('dataProvider').subscribe((event) => {
>   console.log('Data Provider Event:', event);
>   console.log('Module Name:', event.moduleName);
> });
>
> // Unsubscribe when done
> subscription?.unsubscribe();
> ```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.