> ## 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.
</Warning>

# 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`](/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`](/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 `url` string and `headers` object                                 |
| **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.

### getConfig

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

* Type: [`ResolverEndpointConfig`](/api-reference/sdk/models/data-models#resolverendpointconfig)
* Request body format: [`GetUserResolverRequest`](/api-reference/sdk/models/data-models#getuserresolverrequest) - `{ organizationId: string, userIds: string[] }`
* Response format: [`ResolverResponse<Record<string, User>>`](/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`.
</Note>

<Tabs>
  <Tab title="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>

  <Tab title="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 });
    ```
  </Tab>
</Tabs>

### Endpoint based Complete Example

<Tabs>
  <Tab title="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>

  <Tab title="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 });
    ```
  </Tab>
</Tabs>

## 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>>>`](/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>>`](/api-reference/sdk/models/data-models#resolverresponse) - Returns `{ data, success, statusCode, message?, timestamp? }`
</Note>

<Tabs>
  <Tab title="Frontend Example">
    <CodeGroup>
      ```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 });
      ```
    </CodeGroup>
  </Tab>

  <Tab title="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>

  <Tab title="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);
    ```
  </Tab>
</Tabs>

### config

Configuration for the user data provider.

* Type: [`ResolverConfig`](/api-reference/sdk/models/data-models#resolverconfig). Relevant properties:
  * `resolveUsersConfig`: [`ResolveUsersConfig`](/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](/async-collaboration/comments/customize-behavior#customautocompletesearch) instead.
    * `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

<Tabs>
  <Tab title="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>

  <Tab title="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 });
    ```
  </Tab>
</Tabs>

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

<Tabs>
  <Tab title="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>

  <Tab title="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>
    ```
  </Tab>
</Tabs>

# Sample Data

<Tabs>
  <Tab title="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>

  <Tab title="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.
  </Tab>
</Tabs>

# 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`](/api-reference/sdk/models/data-models#userresolvermodulename)

<Tabs>
  <Tab title="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>

  <Tab title="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();
    ```
  </Tab>
</Tabs>
