# Store user metadata (/en/realtime-media/rtm/build/manage-presence-and-metadata/storage/store-user-metadata)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="[&#x22;web&#x22;,&#x22;android&#x22;,&#x22;ios&#x22;,&#x22;macos&#x22;,&#x22;flutter&#x22;,&#x22;windows&#x22;,&#x22;linux-cpp&#x22;,&#x22;unity&#x22;]" showTabs="true">
  <_PlatformPanel platform="web">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />

    The Signaling storage service enables you to store and share contextual user data in your app, such as name, date-of-birth, avatar, and connections. When user metadata is set, updated, or deleted, the SDK triggers a storage event notification. Other users in the channel receive this notification within 100ms and use the information according to your business logic.

    ## Understand the tech [#understand-the-tech]

    Use metadata enables you to store and share user level information. A set of user metadata facilitates business-level data storage and real-time notifications. Each user has only one set of user metadata, but each set may contain multiple metadata items. For relevant restrictions, refer to the [API usage restrictions](../../../reference/limitations). Each metadata item has `key`, `value`, and `revision` properties.

    User metadata is stored permanently in the Signaling database. The data persists even after a user logs out. You must explicitly delete it to remove it from the database. This feature impacts your storage billing. Refer to [Pricing](../../../reference/pricing) for details.

    ## Prerequisites [#prerequisites]

    Ensure that you have:

    * Integrated the Signaling SDK in your project .
    * Implemented the framework functionality from the [SDK quickstart](../../../quickstart) page.
    * Enabled storage in [Storage configuration](../../../manage-agora-account#storage-configuration).

    ## Implement user metadata storage [#implement-user-metadata-storage]

    The section shows you to implement user metadata storage in your Signaling app.

    ### Set user metadata [#set-user-metadata]

    To create a new metadata item for the user, or to update the `value` of am existing item, call `setUserMetadata`. This method creates a new item in the user metadata if the specified `key` does not exist, or overwrites the associated `value` if a metadata item with the specified `key` already exists.

    The following example saves a set of metadata items for a specified user. It configures the `options` parameter to add timestamp and modifier information to each metadata item.

    ```javascript
    const Name = {
        key : "Name",
        value : "Tony"
    };

    const Age = {
        key : "Age",
        value : "40"
    };

    const Avatar = {
        key : "Avatar",
        value : "https://your-domain/avatar/tong.png"
    };

    const data = [Name, Age, avatar];

    const options = { userId : "Tony", addTimeStamp : ture, addUserId : true };

    try {
        const result = await rtm.storage.setUserMetadata(data, options);
        console.log(JSON.stringify(result));
    } catch (status) {
        console.log(JSON.stringify(status));
    }
    ```

    The sample code relies on the `await/async` programming model. Enclose it within an asynchronous function for proper execution. When the call is successful, the SDK returns the following data structure:

    ```json
    {
        timestamp : 18770098911,   // Timestamp of successful operation
        userId : "Tony",           // Username
        totalCount : 3             // Number of Metadata Items
    }
    ```

    Additionally, Signaling triggers a `storage` event notification of event type `UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    ### Get user metadata [#get-user-metadata]

    To retrieve all metadata items associated with a specific user, call `getUserMetadata`. Refer to the following example:

    ```javascript
    try {
        const result = await rtm.storage.getUserMetadata({ userId: "Tony" });
        console.log(JSON.stringify(result));
    } catch (status) {
        console.log(JSON.stringify(status));
    }
    ```

    You can also leave the `userId` parameter blank to get the local user's metadata:

    ```javascript
    try {
        const result = await rtm.storage.getUserMetadata();
        console.log(JSON.stringify(result));
    } catch (status) {
        console.log(JSON.stringify(status));
    }
    ```

    Signaling SDK returns the following data structure:

    ```json
    {
        majorRevision: 734874892,
        metadata:{
            {
                key:"Name",
                value:"Tony",
                revision:734874872,
                updateTs:1688978391900,
                authorUid:"Tony"
            },
            {
                key:"Age",
                value:"40",
                revision:734874862,
                updated:1688978390900,
                authorUid:"Tony"
            },
            {
                key:"Avatar",
                value:"https://your-domain/avatar/tony.png",
                revision:734874812,
                updated:1688978382900,
                authorUid:"Tony"
            }
        }
    }
    ```

    ### Update user metadata [#update-user-metadata]

    To modify existing metadata items, call `updateUserMetadata`. If the metadata item does not exist, the SDK returns an error. This method is useful for business use-cases that require permission control on creating new metadata items. For example, the admin defines the user metadata fields and users may only update the values.

    The following example updates the value of an existing metadata item:

    ```javascript
    const Age = {
        key : "Age",
        value : "45"
    };

    const data = [Age];
    const options = { userId : "Tony", addTimeStamp : ture, addUserId : true };

    try {
        const result = await rtm.storage.updateUserMetadata(data, options);
        console.log(JSON.stringify(result));
    } catch (status) {
        console.log(JSON.stringify(status));
    }
    ```

    When the call is successful, the SDK returns the following data structure:

    ```json
    {
        timestamp : 18770098911,   // Timestamp of successful operation
        userId : "Tony",           // Username
        totalCount : 3             // Number of Metadata Items
    }
    ```

    Additionally, Signaling triggers a `storage` event notification of event type `UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    ### Delete user metadata [#delete-user-metadata]

    To delete metadata items that are no longer required, call `removeUserMetadata`. Refer to the following sample code:

    ```javascript
    const Age = {
        key : "Age",
    };

    const data = [Age];

    const options = { userId: "Tony", data : data };

    try {
        const result = await rtm.storage.removeUserMetadata(options);
        console.log(JSON.stringify(result));
    } catch (status) {
        console.log(JSON.stringify(status));
    }
    ```

    Setting the `value` for a metadata item that is being deleted has no effect.

    Signaling triggers a `storage` event notification of event type `UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    To delete the entire set of metadata for a user, do not add any metadata items when calling `removeUserMetadata`. Refer to the following sample code:

    ```javascript
    try {
        const result = await rtm.storage.removeUserMetadata();
        console.log(JSON.stringify(result));
    } catch (status) {
        console.log(JSON.stringify(status));
    }
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        When terminating a user account, it is common to delete the entire set of user's metadata. Once user metadata is deleted, it cannot be recovered. If you need data restoration, back up the metadata before deleting it.
      </CalloutDescription>
    </CalloutContainer>

    ## Receive storage event notifications [#receive-storage-event-notifications]

    To receive storage event notifications, implement an event listener. See [event listeners](/en/api-reference/api-ref/signaling#event-listeners) for details. You only receive user metadata update notifications for users that you have subscribed to.

    #### Event notification mode [#event-notification-mode]

    Currently, Signaling only supports the full data update mode. This means that when a user's metadata is updated, the `data` field in the event notification contains all the metadata of the user.

    ### Subscribe to a user's metadata [#subscribe-to-a-users-metadata]

    To monitor updates to a user's metadata, you subscribe to their metadata. Refer to the following sample code:

    ```javascript
    try {
        const result = await rtm.storage.subscribeUserMetadata("Tony");
        console.log(JSON.stringify(result));
    } catch (status) {
        console.log(JSON.stringify(status));
    }
    ```

    When there are changes in the user metadata, Signaling triggers a `storage` event notification of event type `UPDATE` within 100 ms to inform all users who have subscribed to this user's metadata.

    ### Unsubscribe from a user's metadata [#unsubscribe-from-a-users-metadata]

    When you no longer need to receive notifications about a user's metadata updates, unsubscribe from the users's metadata. Refer to the following sample code:

    ```javascript
    try {
        const result = await rtm.storage.unsubscribeUserMetadata("Tony");
        console.log(JSON.stringify(result));
    } catch (status) {
        console.log(JSON.stringify(status));
    }
    ```

    ## Version control [#version-control]

    Signaling integrates compare-and-set (CAS) version control to manage metadata updates. CAS is a concurrency control mechanism to ensure that updates to a shared resource occur only if the resource is in an expected state. The mechanism works as follows:

    1. The client reads the current version of a data item.
    2. Before making an update, the client compares the current version with the last read version number.
    3. If the versions match, the client proceeds with the update and increments the version number. If they do not match, the update is aborted.

    CAS version control is useful in use-cases that require concurrency management. For instance, consider a dating application where only one user may engage in a chat session with a host. When multiple users attempt to join, only the first request is successful.

    The CAS version control feature provides two independent version control parameters. Set one or more of these values according to the needs of your business use-case:

    * `majorRevision` property in `MetadataOptions` method: Enable version number verification of the entire set of channel metadata.

    * `revision` property of a `MetadataItem[]`: Enable version number verification of a single metadata item.

    When setting user metadata, or a single user metadata item, use the revision attribute to enable or disable version control as follows:

    * To disable CAS verification, use the default value of `-1` for the `revision` parameter.

    * To enable CAS verification, set the `majorRevision` or the `revision` parameter to a positive integer. The SDK updates the corresponding value after successfully verifying the revision number. If the specified revision number does not match the latest revision number in the database, the SDK returns an error.

    The following code snippet demonstrates how to employ `majorRevision` and `revision` for updating user metadata and metadata items:

    ```javascript
    const avatar = {
        key : "Avatar",
        value : "https://your-domain/avatar/tony.png",
        revision : 734874812
    };

    const data = [sessionRequest];

    const options = {
        userId : "Tony",
        addTimeStamp : ture,
        addUserId: true,
        majorRevision : 734874892
    };

    try {
        const result = await rtm.storage.updateUserMetadata(data, options);
        console.log(JSON.stringify(result));
    } catch (status) {
        console.log(JSON.stringify(status));
    }
    ```

    In this example, CAS verification for user metadata and metadata items is enabled by setting `majorRevision` and `revision` parameters to positive integers. Upon receiving the update call request, Signaling first verifies the provided major revision number against the latest value in the database. If there's a mismatch, it returns an error; if the values match, Signaling verifies the `revision` number for each metadata item using a similar logic.

    <CalloutContainer type="info">
      <CalloutDescription>
        When using version control, monitor `storage` notifications to retrieve updated values for `majorRevision` and `revision` to ensure that the latest revision values are used for subsequent operations.
      </CalloutDescription>
    </CalloutContainer>

    ## Reference [#reference]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    ### API reference [#api-reference]

    * [`setUserMetaData`](/en/api-reference/api-ref/signaling#storagesetuserpropsag_platform)

    * [`getUserMetadata`](/en/api-reference/api-ref/signaling#storagegetuserpropsag_platform)

    * [`removeUserMetadata`](/en/api-reference/api-ref/signaling#storageremoveuserpropsag_platform)

    * [`updateUserMetadata`](/en/api-reference/api-ref/signaling#storageupdateuserpropsag_platform)

    * [`subscribeUserMetadata`](/en/api-reference/api-ref/signaling#storagesubscribeuserpropsag_platform)

    * [`unsubscribeUserMetadata`](/en/api-reference/api-ref/signaling#storageunsubscribeuserpropsag_platform)

    * [Metadata](/en/api-reference/api-ref/signaling#storagemetadatapropsag_platform)

    * [Event listeners](/en/api-reference/api-ref/signaling#event-listeners)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="android">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />

    The Signaling storage service enables you to store and share contextual user data in your app, such as name, date-of-birth, avatar, and connections. When user metadata is set, updated, or deleted, the SDK triggers a storage event notification. Other users in the channel receive this notification within 100ms and use the information according to your business logic.

    ## Understand the tech [#understand-the-tech-1]

    Use metadata enables you to store and share user level information. A set of user metadata facilitates business-level data storage and real-time notifications. Each user has only one set of user metadata, but each set may contain multiple metadata items. For relevant restrictions, refer to the [API usage restrictions](../../../reference/limitations). Each metadata item has `key`, `value`, and `revision` properties.

    User metadata is stored permanently in the Signaling database. The data persists even after a user logs out. You must explicitly delete it to remove it from the database. This feature impacts your storage billing. Refer to [Pricing](../../../reference/pricing) for details.

    ## Prerequisites [#prerequisites-1]

    Ensure that you have:

    * Integrated the Signaling SDK in your project .
    * Implemented the framework functionality from the [SDK quickstart](../../../quickstart) page.
    * Enabled storage in [Storage configuration](../../../manage-agora-account#storage-configuration).

    ## Implement user metadata storage [#implement-user-metadata-storage-1]

    The section shows you to implement user metadata storage in your Signaling app.

    ### Set user metadata [#set-user-metadata-1]

    To create a new metadata item for the user, or to update the `value` of am existing item, call `setUserMetadata`. This method creates a new item in the user metadata if the specified `key` does not exist, or overwrites the associated `value` if a metadata item with the specified `key` already exists.

    The following example saves a set of metadata items for a specified user. It configures the `options` parameter to add timestamp and modifier information to each metadata item.

    <CodeBlockTabs defaultValue="Java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Java">
        ```java
        Metadata metadata = new Metadata();
        metadata.getItems().add(new MetadataItem("Name", "Tony"));
        metadata.getItems().add(new MetadataItem("Age", "40"));
        metadata.getItems().add(new MetadataItem("Avatar", "https://your-domain/avatar/tony.png"));

        MetadataOptions options = new MetadataOptions();
        options.setRecordTs(true);
        options.setRecordUserId(true);

        mRtmClient.getStorage().setUserMetadata("Tony", metadata, options, new ResultCallback<Void>() {
            @Override
            public void onSuccess(Void responseInfo) {
                log(CALLBACK, "Set user metadata success");
            }

            @Override
            public void onFailure(ErrorInfo errorInfo) {
                log(ERROR, "Failed to set user metadata: " + errorInfo.toString());
            }
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Kotlin">
        ```kotlin
        val metadata = Metadata().apply {
            items.add(MetadataItem("Name", "Tony"))
            items.add(MetadataItem("Age", "40"))
            items.add(MetadataItem("Avatar", "https://your-domain/avatar/tony.png"))
        }

        val options = MetadataOptions().apply {
            setRecordTs(true)
            setRecordUserId(true)
        }

        mRtmClient.getStorage().setUserMetadata("Tony", metadata, options, object : ResultCallback<Void> {
            override fun onSuccess(responseInfo: Void) {
                log(CALLBACK, "Set user metadata success")
            }

            override fun onFailure(errorInfo: ErrorInfo) {
                log(ERROR, "Failed to set user metadata: " + errorInfo.toString())
            }
        })
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    The `onSuccess` callback notifies you of the successful completion of the storage operation. Additionally, Signaling triggers an `onStorageEvent` notification of event type `UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    ### Get user metadata [#get-user-metadata-1]

    To retrieve all metadata items associated with a specific user, call `getUserMetadata`. Refer to the following example:

    <CodeBlockTabs defaultValue="Java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Java">
        ```java
        mRtmClient.getStorage().getUserMetadata("Tony", new ResultCallback<Metadata>() {
            @Override
            public void onSuccess(Metadata data) {
                log(CALLBACK, "Get user metadata success");
                log(INFO, "Major revision: " + data.getMajorRevision());
                for (MetadataItem item : data.getItems()) {
                    log(INFO, item.toString());
                }
            }

            @Override
            public void onFailure(ErrorInfo errorInfo) {
                log(ERROR, "Failed to get user metadata: " + errorInfo.toString());
            }
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Kotlin">
        ```kotlin
        mRtmClient.getStorage().getUserMetadata("Tony", object : ResultCallback<Metadata> {
            override fun onSuccess(data: Metadata) {
                log(CALLBACK, "Get user metadata success")
                log(INFO, "Major revision: " + data.majorRevision)
                data.items.forEach { item ->
                    log(INFO, item.toString())
                }
            }

            override fun onFailure(errorInfo: ErrorInfo) {
                log(ERROR, "Failed to get user metadata: " + errorInfo.toString())
            }
        })
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    You can also leave the `userId` parameter blank to get the local user's metadata:

    <CodeBlockTabs defaultValue="Java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Java">
        ```java
        mRtmClient.getStorage().getUserMetadata("", new ResultCallback<Metadata>() {
            @Override
            public void onSuccess(Metadata data) {
                log(CALLBACK, "Get user metadata success");
            }

            @Override
            public void onFailure(ErrorInfo errorInfo) {
                log(ERROR, errorInfo.toString());
            }
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Kotlin">
        ```kotlin
        mRtmClient.getStorage().getUserMetadata("", object : ResultCallback<Metadata> {
            override fun onSuccess(data: Metadata) {
                log(CALLBACK, "Get user metadata success")
            }

            override fun onFailure(errorInfo: ErrorInfo) {
                log(ERROR, errorInfo.toString())
            }
        })
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    Signaling SDK returns the following data structure:

    ```json
    {
        majorRevision: 734874892,
        metadata:{
            {
                key:"Name",
                value:"Tony",
                revision:734874872,
                updateTs:1688978391900,
                authorUid:"Tony"
            },
            {
                key:"Age",
                value:"40",
                revision:734874862,
                updated:1688978390900,
                authorUid:"Tony"
            },
            {
                key:"Avatar",
                value:"https://your-domain/avatar/tony.png",
                revision:734874812,
                updated:1688978382900,
                authorUid:"Tony"
            }
        }
    }
    ```

    ### Update user metadata [#update-user-metadata-1]

    To modify existing metadata items, call `updateUserMetadata`. If the metadata item does not exist, the SDK returns an error. This method is useful for business use-cases that require permission control on creating new metadata items. For example, the admin defines the user metadata fields and users may only update the values.

    The following example updates the value of an existing metadata item:

    <CodeBlockTabs defaultValue="Java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Java">
        ```java
        Metadata metadata = new Metadata();
        metadata.getItems().add(new MetadataItem("Age", "45"));

        mRtmClient.getStorage().updateUserMetadata("Tony", metadata, new MetadataOptions(true, true), new ResultCallback<Void>() {
            @Override
            public void onSuccess(Void responseInfo) {
                log(CALLBACK, "update user metadata success");
            }

            @Override
            public void onFailure(ErrorInfo errorInfo) {
                log(ERROR, errorInfo.toString());
            }
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Kotlin">
        ```kotlin
        val metadata = Metadata().apply {
            items.add(MetadataItem("Age", "45"))
        }

        mRtmClient.getStorage().updateUserMetadata("Tony", metadata, MetadataOptions(true, true), object : ResultCallback<Void> {
            override fun onSuccess(responseInfo: Void?) {
                log(CALLBACK, "update user metadata success")
            }

            override fun onFailure(errorInfo: ErrorInfo) {
                log(ERROR, errorInfo.toString())
            }
        })
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    The `onSuccess` callback notifies you of the successful completion of the storage operation. Additionally, Signaling triggers an `onStorageEvent` notification of event type `UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    ### Delete user metadata [#delete-user-metadata-1]

    To delete metadata items that are no longer required, call `removeUserMetadata`. Refer to the following sample code:

    <CodeBlockTabs defaultValue="Java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Java">
        ```java
        Metadata metadata = new Metadata();
        MetadataItem age = new MetadataItem();
        age.setKey("Age");
        metadata.getItems().add(age);

        mRtmClient.getStorage().removeUserMetadata("Tony", metadata, new MetadataOptions(true, true), new ResultCallback<Void>() {
            @Override
            public void onSuccess(Void responseInfo) {
                log(CALLBACK, "remove user metadata success");
            }

            @Override
            public void onFailure(ErrorInfo errorInfo) {
                log(ERROR, errorInfo.toString());
            }
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Kotlin">
        ```kotlin
        val metadata = Metadata().apply {
            val age = MetadataItem().apply { key = "Age" }
            items.add(age)
        }

        mRtmClient.getStorage().removeUserMetadata("Tony", metadata, MetadataOptions(true, true), object : ResultCallback<Void> {
            override fun onSuccess(responseInfo: Void?) {
                log(CALLBACK, "remove user metadata success")
            }

            override fun onFailure(errorInfo: ErrorInfo) {
                log(ERROR, errorInfo.toString())
            }
        })
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    Setting the `value` for a metadata item that is being deleted has no effect.

    The `onSuccess` callback notifies you of the successful completion of the storage operation. Additionally, Signaling triggers an `onStorageEvent` notification of event type `UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    To delete the entire set of metadata for a user, do not add any metadata items when calling `removeUserMetadata`. Refer to the following sample code:

    <CodeBlockTabs defaultValue="Java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Java">
        ```java
        Metadata metadata = new Metadata();

        mRtmClient.getStorage().removeUserMetadata("Tony", metadata, null, new ResultCallback<Void>() {
            @Override
            public void onSuccess(Void responseInfo) {
                log(CALLBACK, "remove user metadata success");
            }

            @Override
            public void onFailure(ErrorInfo errorInfo) {
                log(ERROR, errorInfo.toString());
            }
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Kotlin">
        ```kotlin
        val metadata = Metadata()

        mRtmClient.getStorage().removeUserMetadata("Tony", metadata, null, object : ResultCallback<Void> {
            override fun onSuccess(responseInfo: Void?) {
                log(CALLBACK, "remove user metadata success")
            }

            override fun onFailure(errorInfo: ErrorInfo) {
                log(ERROR, errorInfo.toString())
            }
        })
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <CalloutContainer type="info">
      <CalloutDescription>
        When terminating a user account, it is common to delete the entire set of user's metadata. Once user metadata is deleted, it cannot be recovered. If you need data restoration, back up the metadata before deleting it.
      </CalloutDescription>
    </CalloutContainer>

    ## Receive storage event notifications [#receive-storage-event-notifications-1]

    A storage event notification returns the [StorageEvent](/en/api-reference/api-ref/signaling#configstorageeventpropsag_platform) data structure, which includes the [RtmStorageEventType](/en/api-reference/api-ref/signaling#enumvstorageeventtypepropsag_platform) parameter.

    To receive storage event notifications, implement an event listener. See [event listeners](/en/api-reference/api-ref/signaling#event-listeners) for details. You only receive user metadata update notifications for users that you have subscribed to.

    #### Event notification mode [#event-notification-mode-1]

    Currently, Signaling only supports the full data update mode. This means that when a user's metadata is updated, the `data` field in the event notification contains all the metadata of the user.

    ### Subscribe to a user's metadata [#subscribe-to-a-users-metadata-1]

    To monitor updates to a user's metadata, you subscribe to their metadata. Refer to the following sample code:

    <CodeBlockTabs defaultValue="Java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Java">
        ```java
        mRtmClient.getStorage().subscribeUserMetadata("Tony", new ResultCallback<Void>() {
            @Override
            public void onSuccess(Void responseInfo) {
                log(CALLBACK, "subscribe user metadata success");
            }

            @Override
            public void onFailure(ErrorInfo errorInfo) {
                log(ERROR, errorInfo.toString());
            }
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Kotlin">
        ```kotlin
        mRtmClient.getStorage().subscribeUserMetadata("Tony", object : ResultCallback<Void> {
            override fun onSuccess(responseInfo: Void?) {
                log(CALLBACK, "subscribe user metadata success")
            }

            override fun onFailure(errorInfo: ErrorInfo) {
                log(ERROR, errorInfo.toString())
            }
        })
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    When there are changes in the user metadata, Signaling triggers an `onStorageEvent` notification of event type `UPDATE` within 100 ms to inform all users who have subscribed to this user's metadata.

    ### Unsubscribe from a user's metadata [#unsubscribe-from-a-users-metadata-1]

    When you no longer need to receive notifications about a user's metadata updates, unsubscribe from the users's metadata. Refer to the following sample code:

    <CodeBlockTabs defaultValue="Java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Java">
        ```java
        mRtmClient.getStorage().unsubscribeUserMetadata("Tony", new ResultCallback<Void>() {
            @Override
            public void onSuccess(Void responseInfo) {
                log(CALLBACK, "unsubscribe user metadata success");
            }

            @Override
            public void onFailure(ErrorInfo errorInfo) {
                log(ERROR, errorInfo.toString());
            }
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Kotlin">
        ```kotlin
        mRtmClient.getStorage().unsubscribeUserMetadata("Tony", object : ResultCallback<Void> {
            override fun onSuccess(responseInfo: Void?) {
                log(CALLBACK, "unsubscribe user metadata success")
            }

            override fun onFailure(errorInfo: ErrorInfo) {
                log(ERROR, errorInfo.toString())
            }
        })
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ## Version control [#version-control-1]

    Signaling integrates compare-and-set (CAS) version control to manage metadata updates. CAS is a concurrency control mechanism to ensure that updates to a shared resource occur only if the resource is in an expected state. The mechanism works as follows:

    1. The client reads the current version of a data item.
    2. Before making an update, the client compares the current version with the last read version number.
    3. If the versions match, the client proceeds with the update and increments the version number. If they do not match, the update is aborted.

    CAS version control is useful in use-cases that require concurrency management. For instance, consider a dating application where only one user may engage in a chat session with a host. When multiple users attempt to join, only the first request is successful.

    The CAS version control feature provides two independent version control parameters. Set one or more of these values according to the needs of your business use-case:

    * `majorRevision` parameter in the `setMajorRevision` method: Enable version number verification of the entire set of user metadata.

    * `revision` parameter of a `MetadataItem`: Enable version number verification of a single metadata item.

    When setting user metadata, or a single user metadata item, use the revision attribute to enable or disable version control as follows:

    * To disable CAS verification, use the default value of `-1` for the `revision` parameter.

    * To enable CAS verification, set the `majorRevision` or the `revision` parameter to a positive integer. The SDK updates the corresponding value after successfully verifying the revision number. If the specified revision number does not match the latest revision number in the database, the SDK returns an error.

    The following code snippet demonstrates how to employ `majorRevision` and `revision` for updating user metadata and metadata items:

    <CodeBlockTabs defaultValue="Java">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Java">
          Java
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Kotlin">
          Kotlin
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Java">
        ```java
        Metadata metadata = new Metadata();
        metadata.setMajorRevision(734874892);
        metadata.getItems().add(new MetadataItem("Avatar", "https://your-domain/avatar/tony.png", 734874812));

        MetadataOptions options = new MetadataOptions();
        options.setRecordTs(true);
        options.setRecordUserId(true);
        mRtmClient.getStorage().updateUserMetadata("Tony", metadata, options, new ResultCallback<Void>() {
            @Override
            public void onSuccess(Void responseInfo) {
                log(CALLBACK, "update user metadata success");
            }

            @Override
            public void onFailure(ErrorInfo errorInfo) {
                log(ERROR, errorInfo.toString());
            }
        });
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Kotlin">
        ```kotlin
        val metadata = Metadata()
        metadata.majorRevision = 734874892
        metadata.items.add(MetadataItem("Avatar", "https://your-domain/avatar/tony.png", 734874812))

        val options = MetadataOptions()
        options.recordTs = true
        options.recordUserId = true
        mRtmClient.getStorage().updateUserMetadata("Tony", metadata, options, object : ResultCallback<Void> {
            override fun onSuccess(responseInfo: Void?) {
                log(CALLBACK, "update user metadata success")
            }

            override fun onFailure(errorInfo: ErrorInfo) {
                log(ERROR, errorInfo.toString())
            }
        })
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    In this example, CAS verification for user metadata and metadata items is enabled by setting `majorRevision` and `revision` parameters to positive integers. Upon receiving the update call request, Signaling first verifies the provided major revision number against the latest value in the database. If there's a mismatch, it returns an error; if the values match, Signaling verifies the `revision` number for each metadata item using a similar logic.

    <CalloutContainer type="info">
      <CalloutDescription>
        When using version control, monitor `onStorageEvent` notifications to retrieve updated values for `majorRevision` and `revision` to ensure that the latest revision values are used for subsequent operations.
      </CalloutDescription>
    </CalloutContainer>

    ## Reference [#reference-1]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    ### API reference [#api-reference-1]

    * [`setUserMetaData`](/en/api-reference/api-ref/signaling#storagesetuserpropsag_platform)

    * [`getUserMetadata`](/en/api-reference/api-ref/signaling#storagegetuserpropsag_platform)

    * [`removeUserMetadata`](/en/api-reference/api-ref/signaling#storageremoveuserpropsag_platform)

    * [`updateUserMetadata`](/en/api-reference/api-ref/signaling#storageupdateuserpropsag_platform)

    * [`subscribeUserMetadata`](/en/api-reference/api-ref/signaling#storagesubscribeuserpropsag_platform)

    * [`unsubscribeUserMetadata`](/en/api-reference/api-ref/signaling#storageunsubscribeuserpropsag_platform)

    * [Metadata](/en/api-reference/api-ref/signaling#storagemetadatapropsag_platform)

    * [Event listeners](/en/api-reference/api-ref/signaling#event-listeners)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="ios">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />

    The Signaling storage service enables you to store and share contextual user data in your app, such as name, date-of-birth, avatar, and connections. When user metadata is set, updated, or deleted, the SDK triggers a storage event notification. Other users in the channel receive this notification within 100ms and use the information according to your business logic.

    ## Understand the tech [#understand-the-tech-2]

    Use metadata enables you to store and share user level information. A set of user metadata facilitates business-level data storage and real-time notifications. Each user has only one set of user metadata, but each set may contain multiple metadata items. For relevant restrictions, refer to the [API usage restrictions](../../../reference/limitations). Each metadata item has `key`, `value`, and `revision` properties.

    User metadata is stored permanently in the Signaling database. The data persists even after a user logs out. You must explicitly delete it to remove it from the database. This feature impacts your storage billing. Refer to [Pricing](../../../reference/pricing) for details.

    ## Prerequisites [#prerequisites-2]

    Ensure that you have:

    * Integrated the Signaling SDK in your project .
    * Implemented the framework functionality from the [SDK quickstart](../../../quickstart) page.
    * Enabled storage in [Storage configuration](../../../manage-agora-account#storage-configuration).

    ## Implement user metadata storage [#implement-user-metadata-storage-2]

    The section shows you to implement user metadata storage in your Signaling app.

    ### Set user metadata [#set-user-metadata-2]

    To create a new metadata item for the user, or to update the `value` of am existing item, call `setUserMetadata`. This method creates a new item in the user metadata if the specified `key` does not exist, or overwrites the associated `value` if a metadata item with the specified `key` already exists.

    The following example saves a set of metadata items for a specified user. It configures the `options` parameter to add timestamp and modifier information to each metadata item.

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        let name = AgoraRtmMetadataItem()
        name.key = "Name"
        name.value = "Tony"

        let age = AgoraRtmMetadataItem()
        age.key = "Age"
        age.value = "40"

        let avatar = AgoraRtmMetadataItem()
        avatar.key = "Avatar"
        avatar.value = "https://your-domain/avatar/tong.png"

        let itemArray = [name, age, avatar]
        metadata.items = itemArray

        let metadataOpt = AgoraRtmMetadataOptions()
        metadataOpt.recordUserId = true
        metadataOpt.recordTs = true

        rtm.getStorage()?.setUserMetadata(userId: "Tony", data: metadata, options: metadataOpt) { response, errorInfo in
            if errorInfo == nil {
                print("setUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("setUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform other channel members.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];

        AgoraRtmMetadataItem* Name = [[AgoraRtmMetadataItem alloc] init];
        Name.key = @"Name";
        Name.value = @"Tony";
        AgoraRtmMetadataItem* Age = [[AgoraRtmMetadataItem alloc] init];
        Age.key = @"Age";
        Age.value = @"40";
        AgoraRtmMetadataItem* Avatar = [[AgoraRtmMetadataItem alloc] init];
        Avatar.key = @"Avatar";
        Avatar.value = @"https://your-domain/avatar/tony.png";

        NSArray *item_array = [NSArray arrayWithObjects:Name, Age, Avatar];
        metadata.items = item_array;

        AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
        metadata_opt.recordUserId = true;
        metadata_opt.recordTs = true;
        [[rtm getStorage] setUserMetadata:@"Tony" data:metadata options:metadata_opt completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"setUserMetadata success!!");
            } else {
                NSLog(@"setUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform other channel members.
      </TabsContent>
    </Tabs>

    ### Get user metadata [#get-user-metadata-2]

    To retrieve all metadata items associated with a specific user, call `getUserMetadata`. Refer to the following example:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        // Retrieve user metadata
        rtm.getStorage()?.getUserMetadata(userId: "Tony") { response, errorInfo in
            if errorInfo == nil {
                print("getUserMetadata success!!")
                if let data = response?.data {
                    print("Retrieved metadata: \\(data)")
                }
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("getUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        [[rtm getStorage] getUserMetadata:@"Tony" completion:^(AgoraRtmGetMetadataResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"getUserMetadata success!!");
                AgoraRtmMetadata* data = response.data; //get storage data;
            } else {
                NSLog(@"getUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    You can also leave the `userId` parameter blank to get the local user's metadata:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        rtm.getStorage()?.getUserMetadata(userId: "") { response, errorInfo in
            if errorInfo == nil {
                print("getUserMetadata success!!")
                if let data = response?.data {
                }
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("getUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        [[rtm getStorage] getUserMetadata:@"" completion:^(AgoraRtmGetMetadataResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"getUserMetadata success!!");
                AgoraRtmMetadata* data = response.data; //get storage data;
            } else {
                NSLog(@"getUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    Signaling returns the following data structure:

    ```js
    {
        majorRevision: 734874892,
        metadata:{
            {
                key:"Name",
                value:"Tony",
                revision:734874872,
                updateTs:1688978391900,
                authorUid:"Tony"
            },
            {
                key:"Age",
                value:"40",
                revision:734874862,
                updated:1688978390900,
                authorUid:"Tony"
            },
            {
                key:"Avatar",
                value:"https://your-domain/avatar/tony.png",
                revision:734874812,
                updated:1688978382900,
                authorUid:"Tony"
            }
        }
    }
    ```

    ### Update user metadata [#update-user-metadata-2]

    To modify existing metadata items, call `updateUserMetadata`. If the metadata item does not exist, the SDK returns an error. This method is useful for business use-cases that require permission control on creating new metadata items. For example, the admin defines the user metadata fields and users may only update the values.

    The following example updates the value of an existing metadata item:

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        let age = AgoraRtmMetadataItem()
        age.key = "Age"
        age.value = "45"

        let itemArray = [age]
        metadata.items = itemArray

        let metadataOpt = AgoraRtmMetadataOptions()
        metadataOpt.recordUserId = true
        metadataOpt.recordTs = true

        rtm.getStorage()?.updateUserMetadata(userId: "Tony", data: metadata, options: metadataOpt) { response, errorInfo in
            if errorInfo == nil {
                print("updateUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("updateUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform other channel members.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];

        AgoraRtmMetadataItem* Age = [[AgoraRtmMetadataItem alloc] init];
        Age.key = @"Age";
        Age.value = @"45";
        NSArray *item_array = [NSArray arrayWithObjects:Age];
        metadata.items = item_array;

        AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
        metadata_opt.recordUserId = true;
        metadata_opt.recordTs = true;

        [[rtm getStorage] updateUserMetadata:@"Tony" data:metadata options:metadata_opt completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"updateUserMetadata success!!");
            } else {
                NSLog(@"updateUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform other channel members.
      </TabsContent>
    </Tabs>

    ### Delete user metadata [#delete-user-metadata-2]

    To delete metadata items that are no longer required, call `removeUserMetadata`. Refer to the following sample code:

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        let age = AgoraRtmMetadataItem()
        age.key = "Age"

        let itemArray = [age]
        metadata.items = itemArray

        rtm.getStorage()?.removeUserMetadata(userId: "Tony", data: metadata, options: nil) { response, errorInfo in
            if errorInfo == nil {
                print("removeUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("removeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        Setting the `value` for a metadata item that is being deleted has no effect.

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform other channel members.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];

        AgoraRtmMetadataItem* Age = [[AgoraRtmMetadataItem alloc] init];
        Age.key = @"Age";
        NSArray *item_array = [NSArray arrayWithObjects:Age];
        metadata.items = item_array;

        [[rtm getStorage] removeUserMetadata:@"Tony" data:metadata options:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"removeUserMetadata success!!");
            } else {
                NSLog(@"removeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        Setting the `value` for a metadata item that is being deleted has no effect.

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform other channel members.
      </TabsContent>
    </Tabs>

    To delete the entire set of metadata for a user, do not add any metadata items when calling `removeUserMetadata`. Refer to the following sample code:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        // Remove user metadata
        rtm.getStorage()?.removeUserMetadata(userId: "Tony", data: metadata, options: nil) { response, errorInfo in
            if errorInfo == nil {
                print("removeUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("removeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];
        [[rtm getStorage] removeUserMetadata:@"Tony" data:metadata options:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"removeUserMetadata success!!");
            } else {
                NSLog(@"removeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <CalloutContainer type="info">
      <CalloutDescription>
        When terminating a user account, it is common to delete the entire set of user's metadata. Once user metadata is deleted, it cannot be recovered. To implement data restoration, back up the metadata before deleting it.
      </CalloutDescription>
    </CalloutContainer>

    ## Receive storage event notifications [#receive-storage-event-notifications-2]

    A storage event notification returns the [AgoraRtmStorageEvent](/en/api-reference/api-ref/signaling#configstorageeventpropsag_platform) data structure, which includes the [AgoraRtmStorageEventType](/en/api-reference/api-ref/signaling#enumvstorageeventtypepropsag_platform) parameter.

    To receive storage event notifications, implement an event listener. See [event listeners](/en/api-reference/api-ref/signaling#event-listeners) for details. You only receive user metadata update notifications for users that you have subscribed to.

    #### Event notification mode [#event-notification-mode-2]

    Currently, Signaling only supports full data update mode. This means that when a user's metadata is updated, the `data` field in the event notification contains all the metadata of the user.

    ### Subscribe to a user's metadata [#subscribe-to-a-users-metadata-2]

    To monitor updates to a user's metadata, you subscribe to their metadata. Refer to the following sample code:

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        rtm.getStorage()?.subscribeUserMetadata(userId: "Tony") { response, errorInfo in
            if errorInfo == nil {
                print("subscribeUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("subscribeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        When there are changes in the user metadata, Signaling triggers a `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform all users who have subscribed to this user's metadata.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        [[rtm getStorage] subscribeUserMetadata:@"Tony" completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"subscribeUserMetadata success!!");
            } else {
                NSLog(@"subscribeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        When there are changes in the user metadata, Signaling triggers a `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform all users who have subscribed to this user's metadata.
      </TabsContent>
    </Tabs>

    ### Unsubscribe from a user's metadata [#unsubscribe-from-a-users-metadata-2]

    When you no longer need to receive notifications about a user's metadata updates, unsubscribe from the users's metadata. Refer to the following sample code:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        rtm.getStorage()?.unsubscribeUserMetadata(userId: "Tony") { response, errorInfo in
            if errorInfo == nil {
                print("unsubscribeUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("unsubscribeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        [[rtm getStorage] unsubscribeUserMetadata:@"Tony" completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"unsubscribeUserMetadata success!!");
            } else {
                NSLog(@"unsubscribeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ## Version control [#version-control-2]

    Signaling integrates compare-and-set (CAS) version control to manage metadata updates. CAS is a concurrency control mechanism to ensure that updates to a shared resource occur only if the resource is in an expected state. The mechanism works as follows:

    1. The client reads the current version of a data item.
    2. Before making an update, the client compares the current version with the last read version number.
    3. If the versions match, the client proceeds with the update and increments the version number. If they do not match, the update is aborted.

    CAS version control is useful in use-cases that require concurrency management. For instance, consider a dating application where only one user may engage in a chat session with a host. When multiple users attempt to join, only the first request is successful.

    The CAS version control feature provides two independent version control parameters. Set one or more of these values according to the needs of your business use-case:

    * `majorRevision` parameter in the `setMajorRevision` method: Enable version number verification of the entire set of user metadata.

    * `revision` parameter of a `MetadataItem`: Enable version number verification of a single metadata item.

    When setting user metadata, or a single user metadata item, use the revision attribute to enable or disable version control as follows:

    * To disable CAS verification, use the default value of `-1` for the `revision` parameter.

    * To enable CAS verification, set the `majorRevision` or the `revision` parameter to a positive integer. The SDK updates the corresponding value after successfully verifying the revision number. If the specified revision number does not match the latest revision number in the database, the SDK returns an error.

    The following code snippet demonstrates how to employ `majorRevision` and `revision` for updating user metadata and metadata items:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        let sessionRequest = AgoraRtmMetadataItem()
        // Set user avatar URL as metadata
        sessionRequest.key = "Avatar"
        sessionRequest.value = "https://your-domain/avatar/tony.png"
        sessionRequest.revision = 734874812

        // Create an array of metadata items
        let itemArray = [sessionRequest]
        metadata.items = itemArray
        metadata.majorRevision = 734874892 // Set major revision

        let metadataOptions = AgoraRtmMetadataOptions()
        // Enable recording of user ID and timestamp
        metadataOptions.recordUserId = true
        metadataOptions.recordTs = true

        // Update user metadata
        rtm.getStorage().updateUserMetadata(userId: "Tony", data: metadata, options: metadataOptions) { response, errorInfo in
            if errorInfo == nil {
                print("updateUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("updateUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];
        AgoraRtmMetadataItem* sessionRequest = [[AgoraRtmMetadataItem alloc] init];
        sessionRequest.key = @"Avatar";
        sessionRequest.value = @"https://your-domain/avatar/tony.png";
        sessionRequest.revision = 734874812;
        NSArray *item_array = [NSArray arrayWithObjects:sessionRequest];
        metadata.items = item_array;
        metadata.majorRevision = 734874892; //set major revision

        AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
        metadata_opt.recordUserId = true;
        metadata_opt.recordTs = true;
        [[rtm getStorage] updateUserMetadata:@"Tony" data:metadata options:metadata_opt completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"updateUserMetadata success!!");
            } else {
                NSLog(@"updateUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    In this example, CAS verification for user metadata and metadata items is enabled by setting `majorRevision` and `revision` parameters to positive integers. Upon receiving the update call request, Signaling first verifies the provided major revision number against the latest value in the database. If there's a mismatch, it returns an error; if the values match, Signaling verifies the `revision` number for each metadata item using a similar logic.

    <CalloutContainer type="info">
      <CalloutDescription>
        When using version control, monitor `didReceiveStorageEvent` notifications to retrieve updated values for `majorRevision` and `revision` to ensure that the latest revision values are used for subsequent operations.
      </CalloutDescription>
    </CalloutContainer>

    ## Reference [#reference-2]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    ### API reference [#api-reference-2]

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        * [`setUserMetaData`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagesetuserpropsag_platform)
        * [`getUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagegetuserpropsag_platform)
        * [`removeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storageremoveuserpropsag_platform)
        * [`updateUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storageupdateuserpropsag_platform)
        * [`subscribeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagesubscribeuserpropsag_platform)
        * [`unsubscribeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storageunsubscribeuserpropsag_platform)
        * [`AgoraRtmMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagemetadatapropsag_platform)
      </TabsContent>

      <TabsContent value="objc">
        * [`setUserMetaData`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagesetuserpropsag_platform)
        * [`getUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagegetuserpropsag_platform)
        * [`removeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storageremoveuserpropsag_platform)
        * [`updateUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storageupdateuserpropsag_platform)
        * [`subscribeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagesubscribeuserpropsag_platform)
        * [`unsubscribeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storageunsubscribeuserpropsag_platform)
        * [`AgoraRtmMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagemetadatapropsag_platform)
      </TabsContent>
    </Tabs>

    * [Event listeners](/en/api-reference/api-ref/signaling#event-listeners)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="macos">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="macos" />

    The Signaling storage service enables you to store and share contextual user data in your app, such as name, date-of-birth, avatar, and connections. When user metadata is set, updated, or deleted, the SDK triggers a storage event notification. Other users in the channel receive this notification within 100ms and use the information according to your business logic.

    ## Understand the tech [#understand-the-tech-3]

    Use metadata enables you to store and share user level information. A set of user metadata facilitates business-level data storage and real-time notifications. Each user has only one set of user metadata, but each set may contain multiple metadata items. For relevant restrictions, refer to the [API usage restrictions](../../../reference/limitations). Each metadata item has `key`, `value`, and `revision` properties.

    User metadata is stored permanently in the Signaling database. The data persists even after a user logs out. You must explicitly delete it to remove it from the database. This feature impacts your storage billing. Refer to [Pricing](../../../reference/pricing) for details.

    ## Prerequisites [#prerequisites-3]

    Ensure that you have:

    * Integrated the Signaling SDK in your project .
    * Implemented the framework functionality from the [SDK quickstart](../../../quickstart) page.
    * Enabled storage in [Storage configuration](../../../manage-agora-account#storage-configuration).

    ## Implement user metadata storage [#implement-user-metadata-storage-3]

    The section shows you to implement user metadata storage in your Signaling app.

    ### Set user metadata [#set-user-metadata-3]

    To create a new metadata item for the user, or to update the `value` of am existing item, call `setUserMetadata`. This method creates a new item in the user metadata if the specified `key` does not exist, or overwrites the associated `value` if a metadata item with the specified `key` already exists.

    The following example saves a set of metadata items for a specified user. It configures the `options` parameter to add timestamp and modifier information to each metadata item.

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        let name = AgoraRtmMetadataItem()
        name.key = "Name"
        name.value = "Tony"

        let age = AgoraRtmMetadataItem()
        age.key = "Age"
        age.value = "40"

        let avatar = AgoraRtmMetadataItem()
        avatar.key = "Avatar"
        avatar.value = "https://your-domain/avatar/tong.png"

        let itemArray = [name, age, avatar]
        metadata.items = itemArray

        let metadataOpt = AgoraRtmMetadataOptions()
        metadataOpt.recordUserId = true
        metadataOpt.recordTs = true

        rtm.getStorage()?.setUserMetadata(userId: "Tony", data: metadata, options: metadataOpt) { response, errorInfo in
            if errorInfo == nil {
                print("setUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("setUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform other channel members.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];

        AgoraRtmMetadataItem* Name = [[AgoraRtmMetadataItem alloc] init];
        Name.key = @"Name";
        Name.value = @"Tony";
        AgoraRtmMetadataItem* Age = [[AgoraRtmMetadataItem alloc] init];
        Age.key = @"Age";
        Age.value = @"40";
        AgoraRtmMetadataItem* Avatar = [[AgoraRtmMetadataItem alloc] init];
        Avatar.key = @"Avatar";
        Avatar.value = @"https://your-domain/avatar/tony.png";

        NSArray *item_array = [NSArray arrayWithObjects:Name, Age, Avatar];
        metadata.items = item_array;

        AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
        metadata_opt.recordUserId = true;
        metadata_opt.recordTs = true;
        [[rtm getStorage] setUserMetadata:@"Tony" data:metadata options:metadata_opt completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"setUserMetadata success!!");
            } else {
                NSLog(@"setUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform other channel members.
      </TabsContent>
    </Tabs>

    ### Get user metadata [#get-user-metadata-3]

    To retrieve all metadata items associated with a specific user, call `getUserMetadata`. Refer to the following example:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        // Retrieve user metadata
        rtm.getStorage()?.getUserMetadata(userId: "Tony") { response, errorInfo in
            if errorInfo == nil {
                print("getUserMetadata success!!")
                if let data = response?.data {
                    print("Retrieved metadata: \\(data)")
                }
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("getUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        [[rtm getStorage] getUserMetadata:@"Tony" completion:^(AgoraRtmGetMetadataResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"getUserMetadata success!!");
                AgoraRtmMetadata* data = response.data; //get storage data;
            } else {
                NSLog(@"getUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    You can also leave the `userId` parameter blank to get the local user's metadata:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        rtm.getStorage()?.getUserMetadata(userId: "") { response, errorInfo in
            if errorInfo == nil {
                print("getUserMetadata success!!")
                if let data = response?.data {
                }
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("getUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        [[rtm getStorage] getUserMetadata:@"" completion:^(AgoraRtmGetMetadataResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"getUserMetadata success!!");
                AgoraRtmMetadata* data = response.data; //get storage data;
            } else {
                NSLog(@"getUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    Signaling returns the following data structure:

    ```js
    {
        majorRevision: 734874892,
        metadata:{
            {
                key:"Name",
                value:"Tony",
                revision:734874872,
                updateTs:1688978391900,
                authorUid:"Tony"
            },
            {
                key:"Age",
                value:"40",
                revision:734874862,
                updated:1688978390900,
                authorUid:"Tony"
            },
            {
                key:"Avatar",
                value:"https://your-domain/avatar/tony.png",
                revision:734874812,
                updated:1688978382900,
                authorUid:"Tony"
            }
        }
    }
    ```

    ### Update user metadata [#update-user-metadata-3]

    To modify existing metadata items, call `updateUserMetadata`. If the metadata item does not exist, the SDK returns an error. This method is useful for business use-cases that require permission control on creating new metadata items. For example, the admin defines the user metadata fields and users may only update the values.

    The following example updates the value of an existing metadata item:

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        let age = AgoraRtmMetadataItem()
        age.key = "Age"
        age.value = "45"

        let itemArray = [age]
        metadata.items = itemArray

        let metadataOpt = AgoraRtmMetadataOptions()
        metadataOpt.recordUserId = true
        metadataOpt.recordTs = true

        rtm.getStorage()?.updateUserMetadata(userId: "Tony", data: metadata, options: metadataOpt) { response, errorInfo in
            if errorInfo == nil {
                print("updateUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("updateUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform other channel members.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];

        AgoraRtmMetadataItem* Age = [[AgoraRtmMetadataItem alloc] init];
        Age.key = @"Age";
        Age.value = @"45";
        NSArray *item_array = [NSArray arrayWithObjects:Age];
        metadata.items = item_array;

        AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
        metadata_opt.recordUserId = true;
        metadata_opt.recordTs = true;

        [[rtm getStorage] updateUserMetadata:@"Tony" data:metadata options:metadata_opt completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"updateUserMetadata success!!");
            } else {
                NSLog(@"updateUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform other channel members.
      </TabsContent>
    </Tabs>

    ### Delete user metadata [#delete-user-metadata-3]

    To delete metadata items that are no longer required, call `removeUserMetadata`. Refer to the following sample code:

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        let age = AgoraRtmMetadataItem()
        age.key = "Age"

        let itemArray = [age]
        metadata.items = itemArray

        rtm.getStorage()?.removeUserMetadata(userId: "Tony", data: metadata, options: nil) { response, errorInfo in
            if errorInfo == nil {
                print("removeUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("removeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        Setting the `value` for a metadata item that is being deleted has no effect.

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform other channel members.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];

        AgoraRtmMetadataItem* Age = [[AgoraRtmMetadataItem alloc] init];
        Age.key = @"Age";
        NSArray *item_array = [NSArray arrayWithObjects:Age];
        metadata.items = item_array;

        [[rtm getStorage] removeUserMetadata:@"Tony" data:metadata options:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"removeUserMetadata success!!");
            } else {
                NSLog(@"removeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        Setting the `value` for a metadata item that is being deleted has no effect.

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers a `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform other channel members.
      </TabsContent>
    </Tabs>

    To delete the entire set of metadata for a user, do not add any metadata items when calling `removeUserMetadata`. Refer to the following sample code:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        // Remove user metadata
        rtm.getStorage()?.removeUserMetadata(userId: "Tony", data: metadata, options: nil) { response, errorInfo in
            if errorInfo == nil {
                print("removeUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("removeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];
        [[rtm getStorage] removeUserMetadata:@"Tony" data:metadata options:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"removeUserMetadata success!!");
            } else {
                NSLog(@"removeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <CalloutContainer type="info">
      <CalloutDescription>
        When terminating a user account, it is common to delete the entire set of user's metadata. Once user metadata is deleted, it cannot be recovered. To implement data restoration, back up the metadata before deleting it.
      </CalloutDescription>
    </CalloutContainer>

    ## Receive storage event notifications [#receive-storage-event-notifications-3]

    A storage event notification returns the [AgoraRtmStorageEvent](/en/api-reference/api-ref/signaling#configstorageeventpropsag_platform) data structure, which includes the [AgoraRtmStorageEventType](/en/api-reference/api-ref/signaling#enumvstorageeventtypepropsag_platform) parameter.

    To receive storage event notifications, implement an event listener. See [event listeners](/en/api-reference/api-ref/signaling#event-listeners) for details. You only receive user metadata update notifications for users that you have subscribed to.

    #### Event notification mode [#event-notification-mode-3]

    Currently, Signaling only supports full data update mode. This means that when a user's metadata is updated, the `data` field in the event notification contains all the metadata of the user.

    ### Subscribe to a user's metadata [#subscribe-to-a-users-metadata-3]

    To monitor updates to a user's metadata, you subscribe to their metadata. Refer to the following sample code:

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        rtm.getStorage()?.subscribeUserMetadata(userId: "Tony") { response, errorInfo in
            if errorInfo == nil {
                print("subscribeUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("subscribeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        When there are changes in the user metadata, Signaling triggers a `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform all users who have subscribed to this user's metadata.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        [[rtm getStorage] subscribeUserMetadata:@"Tony" completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"subscribeUserMetadata success!!");
            } else {
                NSLog(@"subscribeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        When there are changes in the user metadata, Signaling triggers a `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform all users who have subscribed to this user's metadata.
      </TabsContent>
    </Tabs>

    ### Unsubscribe from a user's metadata [#unsubscribe-from-a-users-metadata-3]

    When you no longer need to receive notifications about a user's metadata updates, unsubscribe from the users's metadata. Refer to the following sample code:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        rtm.getStorage()?.unsubscribeUserMetadata(userId: "Tony") { response, errorInfo in
            if errorInfo == nil {
                print("unsubscribeUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("unsubscribeUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        [[rtm getStorage] unsubscribeUserMetadata:@"Tony" completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"unsubscribeUserMetadata success!!");
            } else {
                NSLog(@"unsubscribeUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ## Version control [#version-control-3]

    Signaling integrates compare-and-set (CAS) version control to manage metadata updates. CAS is a concurrency control mechanism to ensure that updates to a shared resource occur only if the resource is in an expected state. The mechanism works as follows:

    1. The client reads the current version of a data item.
    2. Before making an update, the client compares the current version with the last read version number.
    3. If the versions match, the client proceeds with the update and increments the version number. If they do not match, the update is aborted.

    CAS version control is useful in use-cases that require concurrency management. For instance, consider a dating application where only one user may engage in a chat session with a host. When multiple users attempt to join, only the first request is successful.

    The CAS version control feature provides two independent version control parameters. Set one or more of these values according to the needs of your business use-case:

    * `majorRevision` parameter in the `setMajorRevision` method: Enable version number verification of the entire set of user metadata.

    * `revision` parameter of a `MetadataItem`: Enable version number verification of a single metadata item.

    When setting user metadata, or a single user metadata item, use the revision attribute to enable or disable version control as follows:

    * To disable CAS verification, use the default value of `-1` for the `revision` parameter.

    * To enable CAS verification, set the `majorRevision` or the `revision` parameter to a positive integer. The SDK updates the corresponding value after successfully verifying the revision number. If the specified revision number does not match the latest revision number in the database, the SDK returns an error.

    The following code snippet demonstrates how to employ `majorRevision` and `revision` for updating user metadata and metadata items:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        let sessionRequest = AgoraRtmMetadataItem()
        // Set user avatar URL as metadata
        sessionRequest.key = "Avatar"
        sessionRequest.value = "https://your-domain/avatar/tony.png"
        sessionRequest.revision = 734874812

        // Create an array of metadata items
        let itemArray = [sessionRequest]
        metadata.items = itemArray
        metadata.majorRevision = 734874892 // Set major revision

        let metadataOptions = AgoraRtmMetadataOptions()
        // Enable recording of user ID and timestamp
        metadataOptions.recordUserId = true
        metadataOptions.recordTs = true

        // Update user metadata
        rtm.getStorage().updateUserMetadata(userId: "Tony", data: metadata, options: metadataOptions) { response, errorInfo in
            if errorInfo == nil {
                print("updateUserMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("updateUserMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];
        AgoraRtmMetadataItem* sessionRequest = [[AgoraRtmMetadataItem alloc] init];
        sessionRequest.key = @"Avatar";
        sessionRequest.value = @"https://your-domain/avatar/tony.png";
        sessionRequest.revision = 734874812;
        NSArray *item_array = [NSArray arrayWithObjects:sessionRequest];
        metadata.items = item_array;
        metadata.majorRevision = 734874892; //set major revision

        AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
        metadata_opt.recordUserId = true;
        metadata_opt.recordTs = true;
        [[rtm getStorage] updateUserMetadata:@"Tony" data:metadata options:metadata_opt completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"updateUserMetadata success!!");
            } else {
                NSLog(@"updateUserMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    In this example, CAS verification for user metadata and metadata items is enabled by setting `majorRevision` and `revision` parameters to positive integers. Upon receiving the update call request, Signaling first verifies the provided major revision number against the latest value in the database. If there's a mismatch, it returns an error; if the values match, Signaling verifies the `revision` number for each metadata item using a similar logic.

    <CalloutContainer type="info">
      <CalloutDescription>
        When using version control, monitor `didReceiveStorageEvent` notifications to retrieve updated values for `majorRevision` and `revision` to ensure that the latest revision values are used for subsequent operations.
      </CalloutDescription>
    </CalloutContainer>

    ## Reference [#reference-3]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    ### API reference [#api-reference-3]

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        * [`setUserMetaData`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagesetuserpropsag_platform)
        * [`getUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagegetuserpropsag_platform)
        * [`removeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storageremoveuserpropsag_platform)
        * [`updateUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storageupdateuserpropsag_platform)
        * [`subscribeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagesubscribeuserpropsag_platform)
        * [`unsubscribeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storageunsubscribeuserpropsag_platform)
        * [`AgoraRtmMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagemetadatapropsag_platform)
      </TabsContent>

      <TabsContent value="objc">
        * [`setUserMetaData`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagesetuserpropsag_platform)
        * [`getUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagegetuserpropsag_platform)
        * [`removeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storageremoveuserpropsag_platform)
        * [`updateUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storageupdateuserpropsag_platform)
        * [`subscribeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagesubscribeuserpropsag_platform)
        * [`unsubscribeUserMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storageunsubscribeuserpropsag_platform)
        * [`AgoraRtmMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagemetadatapropsag_platform)
      </TabsContent>
    </Tabs>

    * [Event listeners](/en/api-reference/api-ref/signaling#event-listeners)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="flutter">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />

    The Signaling storage service enables you to store and share contextual user data in your app, such as name, date-of-birth, avatar, and connections. When user metadata is set, updated, or deleted, the SDK triggers a storage event notification. Other users in the channel receive this notification within 100ms and use the information according to your business logic.

    ## Understand the tech [#understand-the-tech-4]

    Use metadata enables you to store and share user level information. A set of user metadata facilitates business-level data storage and real-time notifications. Each user has only one set of user metadata, but each set may contain multiple metadata items. For relevant restrictions, refer to the [API usage restrictions](../../../reference/limitations). Each metadata item has `key`, `value`, and `revision` properties.

    User metadata is stored permanently in the Signaling database. The data persists even after a user logs out. You must explicitly delete it to remove it from the database. This feature impacts your storage billing. Refer to [Pricing](../../../reference/pricing) for details.

    ## Prerequisites [#prerequisites-4]

    Ensure that you have:

    * Integrated the Signaling SDK in your project .
    * Implemented the framework functionality from the [SDK quickstart](../../../quickstart) page.
    * Enabled storage in [Storage configuration](../../../manage-agora-account#storage-configuration).

    ## Implement user metadata storage [#implement-user-metadata-storage-4]

    The section shows you to implement user metadata storage in your Signaling app.

    ### Set user metadata [#set-user-metadata-4]

    To create a new metadata item for the user, or to update the `value` of am existing item, call `setUserMetadata`. This method creates a new item in the user metadata if the specified `key` does not exist, or overwrites the associated `value` if a metadata item with the specified `key` already exists.

    The following example saves a set of metadata items for a specified user. It configures the `options` parameter to add timestamp and modifier information to each metadata item.

    ```dart
    var Name = MetadataItem.fromJson({
        'key' : 'Name',
        'value' : 'Tony'
    });

    var Age = MetadataItem.fromJson({
        'key' : 'Age',
        'value' : '40'
    });

    var Avatar = MetadataItem.fromJson({
        'key' : 'Avatar',
        'value' : 'https://your-domain/avatar/tong.png'
    });

    var metadata = [Name,Age,Avatar];

    try{
        var (status,response) = await rtmClient.getStorage.setUserMetadata(
            'Tony',
            metadata,
            recordTs: true,
            recordUserId: true);

        if (status.error == true) {
            print(status);
        } else {
            print(response);
        }
    } catch (e) {
        print('something went wrong: $e');
    }
    ```

    If the operation is successful, Signaling returns the following data structure:

    ```js
    {
        userId : "Tony",
    }
    ```

    Signaling also triggers a `storage` event notification within 100 ms of type `update` to notify other users subscribed to this user's metadata. For details, see [event listeners](/en/api-reference/api-ref/signaling#event-listeners).

    ### Get user metadata [#get-user-metadata-4]

    To retrieve all metadata items associated with a specific user, call `getUserMetadata`. Refer to the following example:

    ```dart
    try{
        var (status,response) = await rtmClient.getStorage.getUserMetadata('Tony');

        if (status.error == true) {
            print(status);
        } else {
            print(response);
        }
    } catch (e) {
        print('something went wrong: $e');
    }
    ```

    Signaling SDK returns the following data structure:

    ```js
    {
        userId: 'Tony',
        data: {
            majorRevision: 734874862,
            itemCount: 3,
            items: [
                {
                    key: 'Name',
                    value: 'Tony',
                    revision: 734874888,
                    updateTs: 1688978391900,
                    authorUserId: 'Tony'
                },
                {
                    key: 'Age',
                    value: '40',
                    revision: 734874862,
                    updated: 1688978390900,
                    authorUid: 'Tomas'
                },
                {
                    key: 'Avatar',
                    value: 'https://your-domain/avatar/tong.png',
                    revision: 734874812,
                    updated: 1688978382900,
                    authorUid: 'Adam'
                }
            ]
        }
    }
    ```

    ### Update user metadata [#update-user-metadata-4]

    To modify existing metadata items, call `updateUserMetadata`. If the metadata item does not exist, the SDK returns an error. This method is useful for business use-cases that require permission control on creating new metadata items. For example, the admin defines the user metadata fields and users may only update the values.

    The following example updates the value of an existing metadata item:

    ```dart
    var Age = MetadataItem.fromJson({
        'key' : 'Age',
        'value' : '45'
    });
    var metadata = [Age];
    try{
        var (status,response) = await rtmClient.getStorage.updateUserMetadata(
            'Tony',
            metadata,
            recordTs: true,
            recordUserId: true);

        if (status.error == true) {
            print(status);
        } else {
            print(response);
        }
    } catch (e) {
        print('something went wrong: $e');
    }
    ```

    Signaling triggers a `storage` event notification within 100 ms of type `update` to notify other users subscribed to this user's metadata. For details, see [event listeners](/en/api-reference/api-ref/signaling#event-listeners).

    ### Delete user metadata [#delete-user-metadata-4]

    To delete metadata items that are no longer required, call `removeUserMetadata`. Refer to the following sample code:

    ```dart
    var Age = MetadataItem.fromJson({ 'key' : 'Age' });

    var metadata = [Age];
    try{
        var (status,response) = await rtmClient.getStorage.removeUserMetadata(
            'Tony',
            metadata:metadata);

        if (status.error == true) {
            print(status);
        } else {
            print(response);
        }
    } catch (e) {
        print('something went wrong: $e');
    }
    ```

    Setting the `value` for a metadata item that is being deleted has no effect.

    To delete the entire set of metadata for a user, do not add any metadata items when calling `removeUserMetadata`. Refer to the following sample code:

    ```dart
    try{
        var (status,response) = await rtmClient.getStorage.removeUserMetadata('Tony');
        if (status.error == true) {
            print(status);
        } else {
            print(response);
        }
    } catch (e) {
        print('something went wrong: $e');
    }
    ```

    Signaling triggers a `storage` event notification within 100 ms of type `update` to notify other users subscribed to this user's metadata. For details, see [event listeners](/en/api-reference/api-ref/signaling#event-listeners).

    <CalloutContainer type="info">
      <CalloutDescription>
        When terminating a user account, it is common to delete the entire set of user's metadata. Once user metadata is deleted, it cannot be recovered. If you need data restoration, back up the metadata before deleting it.
      </CalloutDescription>
    </CalloutContainer>

    ## Receive storage event notifications [#receive-storage-event-notifications-4]

    A storage event notification returns the [StorageEvent](/en/api-reference/api-ref/signaling#configstorageeventpropsag_platform) data structure, which includes the [RtmStorageEventType](/en/api-reference/api-ref/signaling#enumvstorageeventtypepropsag_platform) parameter.

    To receive storage event notifications, implement an event listener. See [event listeners](/en/api-reference/api-ref/signaling#event-listeners) for details. You only receive user metadata update notifications for users that you have subscribed to.

    #### Event notification mode [#event-notification-mode-4]

    Currently, Signaling only supports the full data update mode. This means that when a user's metadata is updated, the `data` field in the event notification contains all the metadata of the user.

    ### Subscribe to a user's metadata [#subscribe-to-a-users-metadata-4]

    To monitor modifications to a user's metadata, you subscribe to their metadata. Refer to the following sample code:

    ```dart
    try{
        var (status,response) = await rtmClient.getStorage.subscribeUserMetadata('Tony');

        if (status.error == true) {
            print(status);
        } else {
            print(response);
        }
    } catch (e) {
        print('something went wrong: $e');
    }
    ```

    When there are changes to the user metadata, Signaling triggers a `storage` notification of event type `update` within 100 ms to inform all users who have subscribed to this user's metadata.

    ### Unsubscribe from a user's metadata [#unsubscribe-from-a-users-metadata-4]

    When you no longer need to receive notifications about a user's metadata updates, unsubscribe from the users's metadata. Refer to the following sample code:

    ```dart
    try{
        var (status,response) = await rtmClient.getStorage.unsubscribeUserMetadata('Tony');

        if (status.error == true) {
            print(status);
        } else {
            print(response);
        }
    } catch (e) {
        print('something went wrong: $e');
    }
    ```

    ## Version control [#version-control-4]

    Signaling integrates compare-and-set (CAS) version control to manage metadata updates. CAS is a concurrency control mechanism to ensure that updates to a shared resource occur only if the resource is in an expected state. The mechanism works as follows:

    1. The client reads the current version of a data item.
    2. Before making an update, the client compares the current version with the last read version number.
    3. If the versions match, the client proceeds with the update and increments the version number. If they do not match, the update is aborted.

    CAS version control is useful in use-cases that require concurrency management. For instance, consider a dating application where only one user may engage in a chat session with a host. When multiple users attempt to join, only the first request is successful.

    The CAS version control feature provides two independent version control parameters. Set one or more of these values according to the needs of your business use-case:

    * `majorRevision` parameter in the `setMajorRevision` method: Enable version number verification of the entire set of user metadata.

    * `revision` parameter of a `MetadataItem`: Enable version number verification of a single metadata item.

    When setting user metadata, or a single user metadata item, use the revision attribute to enable or disable version control as follows:

    * To disable CAS verification, use the default value of `-1` for the `revision` parameter.

    * To enable CAS verification, set the `majorRevision` or the `revision` parameter to a positive integer. The SDK updates the corresponding value after successfully verifying the revision number. If the specified revision number does not match the latest revision number in the storage, the SDK returns an error.

    The following code demonstrates how to employ `majorRevision` and `revision` for updating user metadata and metadata items:

    ```dart
    var Name = MetadataItem.fromJson({
        'key' : 'Name',
        'value' : 'Tony',
        'revision' : 734874812
    });

    var metadata = [Name];

    try{
        var (status,response) = await rtmClient.getStorage.setUserMetadata(
            'Tony',
            metadata,
            majorRevision: 734874892,
            recordTs: true,
            recordUserId: true);

        if (status.error == true) {
            print(status);
        } else {
            print(response);
        }
    } catch (e) {
        print('something went wrong: $e');
    }
    ```

    In this example, CAS verification for user metadata and metadata items is enabled by setting `majorRevision` and `revision` parameters to positive integers. Upon receiving the update call request, Signaling first verifies the provided major revision number against the latest value in storage. If there's a mismatch, it returns an error; if the values match, Signaling verifies the `revision` number for each metadata item using a similar logic.

    <CalloutContainer type="info">
      <CalloutDescription>
        When using version control, monitor `storage` notifications to retrieve updated values for `majorRevision` and `revision` to ensure that the latest revision values are used for subsequent operations.
      </CalloutDescription>
    </CalloutContainer>

    ## Reference [#reference-4]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    ### API reference [#api-reference-4]

    * [`setUserMetaData`](/en/api-reference/api-ref/signaling#storagesetuserpropsag_platform)

    * [`getUserMetadata`](/en/api-reference/api-ref/signaling#storagegetuserpropsag_platform)

    * [`removeUserMetadata`](/en/api-reference/api-ref/signaling#storageremoveuserpropsag_platform)

    * [`updateUserMetadata`](/en/api-reference/api-ref/signaling#storageupdateuserpropsag_platform)

    * [`subscribeUserMetadata`](/en/api-reference/api-ref/signaling#storagesubscribeuserpropsag_platform)

    * [`unsubscribeUserMetadata`](/en/api-reference/api-ref/signaling#storageunsubscribeuserpropsag_platform)

    * [MetadataItem](/en/api-reference/api-ref/signaling#storagemetadataitempropsag_platform)

    * [Event listeners](/en/api-reference/api-ref/signaling#event-listeners)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="windows">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />

    The Signaling storage service enables you to store and share contextual user data in your app, such as name, date-of-birth, avatar, and connections. When user metadata is set, updated, or deleted, the SDK triggers a storage event notification. Other users in the channel receive this notification within 100ms and use the information according to your business logic.

    ## Understand the tech [#understand-the-tech-5]

    Use metadata enables you to store and share user level information. A set of user metadata facilitates business-level data storage and real-time notifications. Each user has only one set of user metadata, but each set may contain multiple metadata items. For relevant restrictions, refer to the [API usage restrictions](../../../reference/limitations). Each metadata item has `key`, `value`, and `revision` properties.

    User metadata is stored permanently in the Signaling database. The data persists even after a user logs out. You must explicitly delete it to remove it from the database. This feature impacts your storage billing. Refer to [Pricing](../../../reference/pricing) for details.

    ## Prerequisites [#prerequisites-5]

    Ensure that you have:

    * Integrated the Signaling SDK in your project .
    * Implemented the framework functionality from the [SDK quickstart](../../../quickstart) page.
    * Enabled storage in [Storage configuration](../../../manage-agora-account#storage-configuration).

    ## Implement user metadata storage [#implement-user-metadata-storage-5]

    The section shows you to implement user metadata storage in your Signaling app.

    ### Set user metadata [#set-user-metadata-5]

    To create a new metadata item for the user, or to update the `value` of am existing item, call `setUserMetadata`. This method creates a new item in the user metadata if the specified `key` does not exist, or overwrites the associated `value` if a metadata item with the specified `key` already exists.

    The following example saves a set of metadata items for a specified user. It configures the `options` parameter to add timestamp and modifier information to each metadata item.

    ```cpp
    Metadata metadata;
    std::vector<agora::rtm::MetadataItem> items;
    items.emplace_back(MetadataItem("Name", "Tony"));
    items.emplace_back(MetadataItem("Age", "40"));
    items.emplace_back(MetadataItem("Avatar", "https://your-domain/avatar/tong.png"));
    metadata.items = items.data();
    metadata.items = items.size();

    MetadataOptions options;
    options.recordTs = true;
    options.recordUserId = true;

    uint64_t requestId;
    rtm_client->getStorage()->setUserMetadata("Tony", metadata, options, requestId);
    ```

    After you call this method, the SDK triggers the `onSetUserMetadataResult` callback to return the call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onSetUserMetadataResult(const uint64_t requestId, const char *userId, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("SetUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("SetUserMetadata success\n");
            }
        }
    };
    ```

    Additionally, Signaling triggers an `onStorageEvent` notification of event type `RTM_STORAGE_EVENT_TYPE_UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    ### Get user metadata [#get-user-metadata-5]

    To retrieve all metadata items associated with a specific user, call `getUserMetadata`. Refer to the following example:

    ```cpp
    uint64_t requestId;
    rtmClient->getStorage()->getUserMetadata("Tony", requestId);
    ```

    After you call this method, the SDK triggers the `onGetUserMetadataResult` callback to return the call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onGetUserMetadataResult(const uint64_t requestId, const char *userId, const Metadata& data, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("GetUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("GetUserMetadata success user id: %s\n", userId);
                for (int i = 0 ; i < data.itemCount; i++) {
                    printf("key: %s value: %s revision: %lld\n", data.items[i].key, data.items[i].value, data.items[i].revision);
                }
            }
        }
    };
    ```

    You can also leave the `userId` parameter blank to get the local user's metadata:

    ```cpp
    uint64_t requestId;
    rtmClient->getStorage()->getUserMetadata("userId", requestId);
    ```

    Signaling SDK returns the following data structure:

    ```js
    {
        majorRevision: 734874892,
        metadata:{
            "Name":{
                value:"Tony",
                revision:734874872,
                updated:1688978391900,
                authorUid:"Tony"
            },
            "Age":{
                value:"40",
                revision:734874862,
                updated:1688978390900,
                authorUid:"Tony"
            },
            "Avatar":{
                value:"https://your-domain/avatar/tong.png",
                revision:734874812,
                updated:1688978382900,
                authorUid:"Tony"
            }
        }
    }
    ```

    ### Update user metadata [#update-user-metadata-5]

    To modify existing metadata items, call `updateUserMetadata`. If the metadata item does not exist, the SDK returns an error. This method is useful for business use-cases that require permission control on creating new metadata items. For example, the admin defines the user metadata fields and users may only update the values.

    The following example updates the value of an existing metadata item:

    ```cpp
    Metadata metadata;
    std::vector<MetadataItem> items;
    items.emplace_back(MetadataItem("Age", "45"));
    metadata.items = items.data();
    metadata.itemCount = items.size();

    MetadataOptions options;
    options.recordTs = true;
    options.recordUserId = true;

    uint64_t requestId;
    rtmClient->getStorage()->updateUserMetadata("Tony", metadata, options, requestId);
    ```

    After you call this method, the SDK triggers the `onUpdateUserMetadataResult` callback to return the call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onUpdateUserMetadataResult(const uint64_t requestId, const char *userId, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("UpdateUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("UpdateUserMetadata success\n");
            }
        }
    };
    ```

    Additionally, Signaling triggers an `onStorageEvent` notification of event type `RTM_STORAGE_EVENT_TYPE_UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    ### Delete user metadata [#delete-user-metadata-5]

    To delete metadata items that are no longer required, call `removeUserMetadata`. Refer to the following sample code:

    ```cpp
    Metadata metadata;
    std::vector<MetadataItem> items;
    MetadataItem item;
    item.key = "Age";
    items.emplace_back(item);
    metadata.items = items.data();
    metadata.itemCount = items.size();

    uint64_t requestId;
    rtmClient->getStorage()->removeUserMetadata("Tony", metadata, MetadataOptions(), requestId);
    ```

    Setting the `value` for a metadata item that is being deleted has no effect.

    After you call this method, the SDK triggers the `onRemoveUserMetadataResult` callback to return the call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onRemoveUserMetadataResult(const uint64_t requestId, const char *userId, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("RemoveUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("RemoveUserMetadata success\n");
            }
        }
    };
    ```

    Additionally, Signaling triggers an `onStorageEvent` notification of event type `RTM_STORAGE_EVENT_TYPE_UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    To delete the entire set of metadata for a user, do not add any metadata items when calling `removeUserMetadata`. Refer to the following sample code:

    ```cpp
    Metadata metadata;
    MetadataOptions options;

    uint64_t requestId;
    rtmClient->getStorage()->removeUserMetadata("Tony", metadata, options, requestId);
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        When terminating a user account, it is common to delete the entire set of user's metadata. Once user metadata is deleted, it cannot be recovered. If you need data restoration, back up the metadata before deleting it.
      </CalloutDescription>
    </CalloutContainer>

    ## Receive storage event notifications [#receive-storage-event-notifications-5]

    A storage event notification returns the [StorageEvent](/en/api-reference/api-ref/signaling#configstorageeventpropsag_platform) data structure, which includes the [RTM\_STORAGE\_EVENT\_TYPE](/en/api-reference/api-ref/signaling#enumvstorageeventtypepropsag_platform) parameter.

    To receive storage event notifications, implement an event listener. See [event listeners](/en/api-reference/api-ref/signaling#event-listeners) for details. You only receive user metadata update notifications for users that you have subscribed to.

    #### Event notification mode [#event-notification-mode-5]

    Currently, Signaling only supports the full data update mode. This means that when a user's metadata is updated, the `data` field in the event notification contains all the metadata of the user.

    ### Subscribe to a user's metadata [#subscribe-to-a-users-metadata-5]

    To monitor updates to a user's metadata, you subscribe to their metadata. Refer to the following sample code:

    ```cpp
    uint64_t requestId;
    rtmClient->getStorage()->subscribeUserMetadata("Tony", requestId);
    ```

    After you call this method, the SDK triggers the `onSubscribeUserMetadataResult` callback to return the call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onSubscribeUserMetadataResult(const uint64_t requestId, const char *userId, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("SubscribeUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("SubscribeUserMetadata success\n");
            }
        }
    };
    ```

    When there are changes in the user metadata, Signaling triggers an `onStorageEvent` notification of event type `RTM_STORAGE_EVENT_TYPE_UPDATE` within 100 ms to inform all users who have subscribed to this user's metadata.

    ### Unsubscribe from a user's metadata [#unsubscribe-from-a-users-metadata-5]

    When you no longer need to receive notifications about a user's metadata updates, unsubscribe from the users's metadata. Refer to the following sample code:

    ```cpp
    uint64_t requestId;
    rtm_client->getStorage()->unsubscribeUserMetadata("Tony", requestId);
    ```

    After you call this method, the SDK triggers the `onUnsubscribeUserMetadataResult`  callback to return the API call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onUnsubscribeUserMetadataResult(const uint64_t requestId, const char* userId, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("UnsubscribeUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("UnsubscribeUserMetadata success\n");
            }
        }
    };
    ```

    ## Version control [#version-control-5]

    Signaling integrates compare-and-set (CAS) version control to manage metadata updates. CAS is a concurrency control mechanism to ensure that updates to a shared resource occur only if the resource is in an expected state. The mechanism works as follows:

    1. The client reads the current version of a data item.
    2. Before making an update, the client compares the current version with the last read version number.
    3. If the versions match, the client proceeds with the update and increments the version number. If they do not match, the update is aborted.

    CAS version control is useful in use-cases that require concurrency management. For instance, consider a dating application where only one user may engage in a chat session with a host. When multiple users attempt to join, only the first request is successful.

    The CAS version control feature provides two independent version control parameters. Set one or more of these values according to the needs of your business use-case:

    * `majorRevision` parameter in the `setMajorRevision` method: Enable version number verification of the entire set of user metadata.

    * `revision` parameter of a `MetadataItem`: Enable version number verification of a single metadata item.

    When setting user metadata, or a single user metadata item, use the revision attribute to enable or disable version control as follows:

    * To disable CAS verification, use the default value of `-1` for the `revision` parameter.

    * To enable CAS verification, set the `majorRevision` or the `revision` parameter to a positive integer. The SDK updates the corresponding value after successfully verifying the revision number. If the specified revision number does not match the latest revision number in the database, the SDK returns an error.

    The following code snippet demonstrates how to employ `majorRevision` and `revision` for updating user metadata and metadata items:

    ```cpp
    Metadata metadata;
    metadata.majorRevision = 734874892;
    std::vector<MetadataItem> items;
    items.emplace_back(MetadataItem("Avatar", "https://your-domain/avatar/tony.png", 734874812));
    metadata.items = items.data();
    metadata.itemCount = items.size();

    MetadataOptions options;
    options.recordTs = true;
    options.recordUserId = true;

    uint64_t requestId;
    rtmClient->getStorage()->updateUserMetadata("Tony", metadata, options, requestId);
    ```

    In this example, CAS verification for user metadata and metadata items is enabled by setting `majorRevision` and `revision` parameters to positive integers. Upon receiving the update call request, Signaling first verifies the provided major revision number against the latest value in the database. If there's a mismatch, it returns an error; if the values match, Signaling verifies the `revision` number for each metadata item using a similar logic.

    <CalloutContainer type="info">
      <CalloutDescription>
        When using version control, monitor `onStorageEvent` notifications to retrieve updated values for `majorRevision` and `revision` to ensure that the latest revision values are used for subsequent operations.
      </CalloutDescription>
    </CalloutContainer>

    ## Reference [#reference-5]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    ### API reference [#api-reference-5]

    * [`setUserMetaData`](/en/api-reference/api-ref/signaling#storagesetuserpropsag_platform)

    * [`getUserMetadata`](/en/api-reference/api-ref/signaling#storagegetuserpropsag_platform)

    * [`removeUserMetadata`](/en/api-reference/api-ref/signaling#storageremoveuserpropsag_platform)

    * [`updateUserMetadata`](/en/api-reference/api-ref/signaling#storageupdateuserpropsag_platform)

    * [`subscribeUserMetadata`](/en/api-reference/api-ref/signaling#storagesubscribeuserpropsag_platform)

    * [`unsubscribeUserMetadata`](/en/api-reference/api-ref/signaling#storageunsubscribeuserpropsag_platform)

    * [Metadata](/en/api-reference/api-ref/signaling#storagemetadatapropsag_platform)

    * [Event listeners](/en/api-reference/api-ref/signaling#event-listeners)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="linux-cpp">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="linux-cpp" />

    The Signaling storage service enables you to store and share contextual user data in your app, such as name, date-of-birth, avatar, and connections. When user metadata is set, updated, or deleted, the SDK triggers a storage event notification. Other users in the channel receive this notification within 100ms and use the information according to your business logic.

    ## Understand the tech [#understand-the-tech-6]

    Use metadata enables you to store and share user level information. A set of user metadata facilitates business-level data storage and real-time notifications. Each user has only one set of user metadata, but each set may contain multiple metadata items. For relevant restrictions, refer to the [API usage restrictions](../../../reference/limitations). Each metadata item has `key`, `value`, and `revision` properties.

    User metadata is stored permanently in the Signaling database. The data persists even after a user logs out. You must explicitly delete it to remove it from the database. This feature impacts your storage billing. Refer to [Pricing](../../../reference/pricing) for details.

    ## Prerequisites [#prerequisites-6]

    Ensure that you have:

    * Integrated the Signaling SDK in your project .
    * Implemented the framework functionality from the [SDK quickstart](../../../quickstart) page.
    * Enabled storage in [Storage configuration](../../../manage-agora-account#storage-configuration).

    ## Implement user metadata storage [#implement-user-metadata-storage-6]

    The section shows you to implement user metadata storage in your Signaling app.

    ### Set user metadata [#set-user-metadata-6]

    To create a new metadata item for the user, or to update the `value` of am existing item, call `setUserMetadata`. This method creates a new item in the user metadata if the specified `key` does not exist, or overwrites the associated `value` if a metadata item with the specified `key` already exists.

    The following example saves a set of metadata items for a specified user. It configures the `options` parameter to add timestamp and modifier information to each metadata item.

    ```cpp
    Metadata metadata;
    std::vector<agora::rtm::MetadataItem> items;
    items.emplace_back(MetadataItem("Name", "Tony"));
    items.emplace_back(MetadataItem("Age", "40"));
    items.emplace_back(MetadataItem("Avatar", "https://your-domain/avatar/tong.png"));
    metadata.items = items.data();
    metadata.items = items.size();

    MetadataOptions options;
    options.recordTs = true;
    options.recordUserId = true;

    uint64_t requestId;
    rtm_client->getStorage()->setUserMetadata("Tony", metadata, options, requestId);
    ```

    After you call this method, the SDK triggers the `onSetUserMetadataResult` callback to return the call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onSetUserMetadataResult(const uint64_t requestId, const char *userId, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("SetUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("SetUserMetadata success\n");
            }
        }
    };
    ```

    Additionally, Signaling triggers an `onStorageEvent` notification of event type `RTM_STORAGE_EVENT_TYPE_UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    ### Get user metadata [#get-user-metadata-6]

    To retrieve all metadata items associated with a specific user, call `getUserMetadata`. Refer to the following example:

    ```cpp
    uint64_t requestId;
    rtmClient->getStorage()->getUserMetadata("Tony", requestId);
    ```

    After you call this method, the SDK triggers the `onGetUserMetadataResult` callback to return the call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onGetUserMetadataResult(const uint64_t requestId, const char *userId, const Metadata& data, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("GetUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("GetUserMetadata success user id: %s\n", userId);
                for (int i = 0 ; i < data.itemCount; i++) {
                    printf("key: %s value: %s revision: %lld\n", data.items[i].key, data.items[i].value, data.items[i].revision);
                }
            }
        }
    };
    ```

    You can also leave the `userId` parameter blank to get the local user's metadata:

    ```cpp
    uint64_t requestId;
    rtmClient->getStorage()->getUserMetadata("userId", requestId);
    ```

    Signaling SDK returns the following data structure:

    ```js
    {
        majorRevision: 734874892,
        metadata:{
            "Name":{
                value:"Tony",
                revision:734874872,
                updated:1688978391900,
                authorUid:"Tony"
            },
            "Age":{
                value:"40",
                revision:734874862,
                updated:1688978390900,
                authorUid:"Tony"
            },
            "Avatar":{
                value:"https://your-domain/avatar/tong.png",
                revision:734874812,
                updated:1688978382900,
                authorUid:"Tony"
            }
        }
    }
    ```

    ### Update user metadata [#update-user-metadata-6]

    To modify existing metadata items, call `updateUserMetadata`. If the metadata item does not exist, the SDK returns an error. This method is useful for business use-cases that require permission control on creating new metadata items. For example, the admin defines the user metadata fields and users may only update the values.

    The following example updates the value of an existing metadata item:

    ```cpp
    Metadata metadata;
    std::vector<MetadataItem> items;
    items.emplace_back(MetadataItem("Age", "45"));
    metadata.items = items.data();
    metadata.itemCount = items.size();

    MetadataOptions options;
    options.recordTs = true;
    options.recordUserId = true;

    uint64_t requestId;
    rtmClient->getStorage()->updateUserMetadata("Tony", metadata, options, requestId);
    ```

    After you call this method, the SDK triggers the `onUpdateUserMetadataResult` callback to return the call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onUpdateUserMetadataResult(const uint64_t requestId, const char *userId, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("UpdateUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("UpdateUserMetadata success\n");
            }
        }
    };
    ```

    Additionally, Signaling triggers an `onStorageEvent` notification of event type `RTM_STORAGE_EVENT_TYPE_UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    ### Delete user metadata [#delete-user-metadata-6]

    To delete metadata items that are no longer required, call `removeUserMetadata`. Refer to the following sample code:

    ```cpp
    Metadata metadata;
    std::vector<MetadataItem> items;
    MetadataItem item;
    item.key = "Age";
    items.emplace_back(item);
    metadata.items = items.data();
    metadata.itemCount = items.size();

    uint64_t requestId;
    rtmClient->getStorage()->removeUserMetadata("Tony", metadata, MetadataOptions(), requestId);
    ```

    Setting the `value` for a metadata item that is being deleted has no effect.

    After you call this method, the SDK triggers the `onRemoveUserMetadataResult` callback to return the call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onRemoveUserMetadataResult(const uint64_t requestId, const char *userId, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("RemoveUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("RemoveUserMetadata success\n");
            }
        }
    };
    ```

    Additionally, Signaling triggers an `onStorageEvent` notification of event type `RTM_STORAGE_EVENT_TYPE_UPDATE` within 100 ms to inform all users who have subscribed to the this user's metadata.

    To delete the entire set of metadata for a user, do not add any metadata items when calling `removeUserMetadata`. Refer to the following sample code:

    ```cpp
    Metadata metadata;
    MetadataOptions options;

    uint64_t requestId;
    rtmClient->getStorage()->removeUserMetadata("Tony", metadata, options, requestId);
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        When terminating a user account, it is common to delete the entire set of user's metadata. Once user metadata is deleted, it cannot be recovered. If you need data restoration, back up the metadata before deleting it.
      </CalloutDescription>
    </CalloutContainer>

    ## Receive storage event notifications [#receive-storage-event-notifications-6]

    A storage event notification returns the [StorageEvent](/en/api-reference/api-ref/signaling#configstorageeventpropsag_platform) data structure, which includes the [RTM\_STORAGE\_EVENT\_TYPE](/en/api-reference/api-ref/signaling#enumvstorageeventtypepropsag_platform) parameter.

    To receive storage event notifications, implement an event listener. See [event listeners](/en/api-reference/api-ref/signaling#event-listeners) for details. You only receive user metadata update notifications for users that you have subscribed to.

    #### Event notification mode [#event-notification-mode-6]

    Currently, Signaling only supports the full data update mode. This means that when a user's metadata is updated, the `data` field in the event notification contains all the metadata of the user.

    ### Subscribe to a user's metadata [#subscribe-to-a-users-metadata-6]

    To monitor updates to a user's metadata, you subscribe to their metadata. Refer to the following sample code:

    ```cpp
    uint64_t requestId;
    rtmClient->getStorage()->subscribeUserMetadata("Tony", requestId);
    ```

    After you call this method, the SDK triggers the `onSubscribeUserMetadataResult` callback to return the call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onSubscribeUserMetadataResult(const uint64_t requestId, const char *userId, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("SubscribeUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("SubscribeUserMetadata success\n");
            }
        }
    };
    ```

    When there are changes in the user metadata, Signaling triggers an `onStorageEvent` notification of event type `RTM_STORAGE_EVENT_TYPE_UPDATE` within 100 ms to inform all users who have subscribed to this user's metadata.

    ### Unsubscribe from a user's metadata [#unsubscribe-from-a-users-metadata-6]

    When you no longer need to receive notifications about a user's metadata updates, unsubscribe from the users's metadata. Refer to the following sample code:

    ```cpp
    uint64_t requestId;
    rtm_client->getStorage()->unsubscribeUserMetadata("Tony", requestId);
    ```

    After you call this method, the SDK triggers the `onUnsubscribeUserMetadataResult`  callback to return the API call result.

    ```cpp
    // Asynchronous callback
    class RtmEventHandler : public IRtmEventHandler {
        void onUnsubscribeUserMetadataResult(const uint64_t requestId, const char* userId, RTM_ERROR_CODE errorCode) override {
            if (errorCode != RTM_ERROR_OK) {
                printf("UnsubscribeUserMetadata failed error is %d reason is %s\n", errorCode, getErrorReason(errorCode));
            } else {
                printf("UnsubscribeUserMetadata success\n");
            }
        }
    };
    ```

    ## Version control [#version-control-6]

    Signaling integrates compare-and-set (CAS) version control to manage metadata updates. CAS is a concurrency control mechanism to ensure that updates to a shared resource occur only if the resource is in an expected state. The mechanism works as follows:

    1. The client reads the current version of a data item.
    2. Before making an update, the client compares the current version with the last read version number.
    3. If the versions match, the client proceeds with the update and increments the version number. If they do not match, the update is aborted.

    CAS version control is useful in use-cases that require concurrency management. For instance, consider a dating application where only one user may engage in a chat session with a host. When multiple users attempt to join, only the first request is successful.

    The CAS version control feature provides two independent version control parameters. Set one or more of these values according to the needs of your business use-case:

    * `majorRevision` parameter in the `setMajorRevision` method: Enable version number verification of the entire set of user metadata.

    * `revision` parameter of a `MetadataItem`: Enable version number verification of a single metadata item.

    When setting user metadata, or a single user metadata item, use the revision attribute to enable or disable version control as follows:

    * To disable CAS verification, use the default value of `-1` for the `revision` parameter.

    * To enable CAS verification, set the `majorRevision` or the `revision` parameter to a positive integer. The SDK updates the corresponding value after successfully verifying the revision number. If the specified revision number does not match the latest revision number in the database, the SDK returns an error.

    The following code snippet demonstrates how to employ `majorRevision` and `revision` for updating user metadata and metadata items:

    ```cpp
    Metadata metadata;
    metadata.majorRevision = 734874892;
    std::vector<MetadataItem> items;
    items.emplace_back(MetadataItem("Avatar", "https://your-domain/avatar/tony.png", 734874812));
    metadata.items = items.data();
    metadata.itemCount = items.size();

    MetadataOptions options;
    options.recordTs = true;
    options.recordUserId = true;

    uint64_t requestId;
    rtmClient->getStorage()->updateUserMetadata("Tony", metadata, options, requestId);
    ```

    In this example, CAS verification for user metadata and metadata items is enabled by setting `majorRevision` and `revision` parameters to positive integers. Upon receiving the update call request, Signaling first verifies the provided major revision number against the latest value in the database. If there's a mismatch, it returns an error; if the values match, Signaling verifies the `revision` number for each metadata item using a similar logic.

    <CalloutContainer type="info">
      <CalloutDescription>
        When using version control, monitor `onStorageEvent` notifications to retrieve updated values for `majorRevision` and `revision` to ensure that the latest revision values are used for subsequent operations.
      </CalloutDescription>
    </CalloutContainer>

    ## Reference [#reference-6]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    ### API reference [#api-reference-6]

    * [`setUserMetaData`](/en/api-reference/api-ref/signaling#storagesetuserpropsag_platform)

    * [`getUserMetadata`](/en/api-reference/api-ref/signaling#storagegetuserpropsag_platform)

    * [`removeUserMetadata`](/en/api-reference/api-ref/signaling#storageremoveuserpropsag_platform)

    * [`updateUserMetadata`](/en/api-reference/api-ref/signaling#storageupdateuserpropsag_platform)

    * [`subscribeUserMetadata`](/en/api-reference/api-ref/signaling#storagesubscribeuserpropsag_platform)

    * [`unsubscribeUserMetadata`](/en/api-reference/api-ref/signaling#storageunsubscribeuserpropsag_platform)

    * [Metadata](/en/api-reference/api-ref/signaling#storagemetadatapropsag_platform)

    * [Event listeners](/en/api-reference/api-ref/signaling#event-listeners)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="unity">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />

    The Signaling storage service enables you to store and share contextual user data in your app, such as name, date-of-birth, avatar, and connections. When user metadata is set, updated, or deleted, the SDK triggers a storage event notification. Other users in the channel receive this notification within 100ms and use the information according to your business logic.

    ## Understand the tech [#understand-the-tech-7]

    Use metadata enables you to store and share user level information. A set of user metadata facilitates business-level data storage and real-time notifications. Each user has only one set of user metadata, but each set may contain multiple metadata items. For relevant restrictions, refer to the [API usage restrictions](../../../reference/limitations). Each metadata item has `key`, `value`, and `revision` properties.

    User metadata is stored permanently in the Signaling database. The data persists even after a user logs out. You must explicitly delete it to remove it from the database. This feature impacts your storage billing. Refer to [Pricing](../../../reference/pricing) for details.

    ## Prerequisites [#prerequisites-7]

    Ensure that you have:

    * Integrated the Signaling SDK in your project .
    * Implemented the framework functionality from the [SDK quickstart](../../../quickstart) page.
    * Enabled storage in [Storage configuration](../../../manage-agora-account#storage-configuration).

    ## Implement user metadata storage [#implement-user-metadata-storage-7]

    The section shows you to implement user metadata storage in your Signaling app.

    ### Set user metadata [#set-user-metadata-7]

    To create a new metadata item for the user, or to update the `value` of am existing item, call `SetUserMetadataAsync`. This method creates a new item in the user metadata if the specified `key` does not exist, or overwrites the associated `value` if a metadata item with the specified `key` already exists.

    The following example saves a set of metadata items for a specified user. It configures the `options` parameter to add timestamp and modifier information to each metadata item.

    ```csharp
    var metadata = new RtmMetadata();

    // Define metadata items
    var name = new MetadataItem()
    {
        key = "Name",
        value = "Tony",
    };
    var age = new MetadataItem()
    {
        key = "Age",
        value = "40",
    };
    var avatar = new MetadataItem()
    {
        key = "Avatar",
        value = "https://your-domain/avatar/tony.png",
    };

    // Add metadata items to the metadata object
    metadata.metadataItems = new MetadataItem[] { name, age, avatar };
    metadata.metadataItemsSize = 3;

    // Configure metadata options
    var options = new MetadataOptions()
    {
        recordUserId = true,
        recordTs = true
    };

    // Set user metadata and handle potential errors
    var (status, response) = await rtmClient.GetStorage().SetUserMetadataAsync("Tony", metadata, options);
    if (status.Error)
    {
        Debug.Log(string.Format("{0} failed, ErrorCode: {1}, due to: {2}", status.Operation, status.ErrorCode, status.Reason));
    }
    else
    {
        Debug.Log(string.Format("Set user :{0} metadata success! ", response.UserId));
    }
    ```

    Signaling triggers an `OnStorageEvent` notification of event type `UPDATE` within 100 ms to inform other channel members.

    ### Get user metadata [#get-user-metadata-7]

    To retrieve all metadata items associated with a specific user, call `GetUserMetadataAsync`. Refer to the following example:

    ```csharp
    var result = await rtmClient.GetStorage().GetUserMetadataAsync("Tony");
    if (result.Status.Error)
    {
        Debug.Log(string.Format("{0} failed, The error code is {1}, because of: {2}", result.Status.Operation, result.Status.ErrorCode, result.Status.Reason));
    }
    else
    {
        Debug.Log(string.Format("Get User :{0} metadata success!  ", result.Response.UserId));
        var data =  result.Response.data;
        if (data.metadataItemsSize != 0)
        {
            Debug.Log(string.Format("Channel Metadata Major Revision is :{0} ! ", data.majorRevision));
            for ( int i =0; i < data.metadataItemsSize; i++)
            {
                Debug.Log(string.Format("The first item Key is:{1}, Value is {2} ! ", i, data.metadataItems[i].key, data.metadataItems[i].value));
            }
        }
    }
    ```

    You can also leave the `userId` parameter blank to get the local user's metadata:

    ```csharp
    var result = await rtmClient.GetStorage().GetUserMetadataAsync("");
    ```

    Signaling SDK returns the following data structure:

    ```js
    {
        majorRevision: 734874892,
        metadata:{
            {
                key:"Name",
                value:"Tony",
                revision:734874872,
                updateTs:1688978391900,
                authorUid:"Tony"
            },
            {
                key:"Age",
                value:"40",
                revision:734874862,
                updated:1688978390900,
                authorUid:"Tony"
            },
            {
                key:"Avatar",
                value:"https://your-domain/avatar/tony.png",
                revision:734874812,
                updated:1688978382900,
                authorUid:"Tony"
            }
        }
    }
    ```

    ### Update user metadata [#update-user-metadata-7]

    To modify existing metadata items, call `UpdateUserMetadataAsync`. If the metadata item does not exist, the SDK returns an error. This method is useful for business use-cases that require permission control on creating new metadata items. For example, the admin defines the user metadata fields and users may only update the values.

    The following example updates the value of an existing metadata item:

    ```csharp
    var metadata = new RtmMetadata();

    // Define the metadata item
    var metadataItem = new MetadataItem()
    {
        key = "Age",
        value = "45",
    };

    // Add the metadata item to the metadata object
    metadata.metadataItems = new MetadataItem[] { metadataItem };
    metadata.metadataItemsSize = 1;

    // Configure metadata options
    var options = new MetadataOptions()
    {
        recordUserId = true,
        recordTs = true
    };

    // Update user metadata and handle potential errors
    var (status, response) = await rtmClient.GetStorage().UpdateUserMetadataAsync("Tony", metadata, options);
    if (status.Error)
    {
        Debug.Log(string.Format("{0} failed, ErrorCode: {1}, due to: {2}", status.Operation, status.ErrorCode, status.Reason));
    }
    else
    {
        Debug.Log(string.Format("Update user: {0} metadata success! ", response.UserId));
    }
    ```

    Signaling triggers an `OnStorageEvent` notification of event type `UPDATE` within 100 ms to inform other channel members.

    ### Delete user metadata [#delete-user-metadata-7]

    To delete metadata items that are no longer required, call `RemoveUserMetadataAsync`. Refer to the following sample code:

    ```csharp
    var metadata = new RtmMetadata();

    // Define the metadata item to be removed
    var metadataItem = new MetadataItem()
    {
        key = "Age",
    };

    // Add the metadata item to the metadata object
    metadata.metadataItems = new MetadataItem[] { metadataItem };
    metadata.metadataItemsSize = 1;

    var options = new MetadataOptions() {};

    // Remove user metadata and handle potential errors
    var (status, response) = await rtmClient.GetStorage().RemoveUserMetadataAsync("Tony", metadata, options);
    if (status.Error)
    {
        Debug.Log(string.Format("{0} failed, ErrorCode: {1}, due to: {2}", status.Operation, status.ErrorCode, status.Reason));
    }
    else
    {
        Debug.Log(string.Format("Remove user: {0} metadata success!", "Tony"));
    }
    ```

    Setting the `value` for a metadata item that is being deleted has no effect.

    Signaling triggers an `OnStorageEvent` notification of event type `UPDATE` within 100 ms to inform other channel members.

    To delete the entire set of metadata for a user, do not add any metadata items when calling `RemoveUserMetadataAsync`. Refer to the following sample code:

    ```csharp
    var metadata = new RtmMetadata();
    var options = new MetadataOptions() {};

    // Remove user metadata and handle potential errors
    var (status, response) = await rtmClient.GetStorage().RemoveUserMetadataAsync("Tony", metadata, options);
    if (status.Error)
    {
        Debug.Log(string.Format("{0} failed, ErrorCode: {1}, due to: {2}", status.Operation, status.ErrorCode, status.Reason));
    }
    else
    {
        Debug.Log(string.Format("Remove user: {0} metadata success!", "Tony"));
    }
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        When terminating a user account, it is common to delete the entire set of user's metadata. Once user metadata is deleted, it cannot be recovered. If you need data restoration, back up the metadata before deleting it.
      </CalloutDescription>
    </CalloutContainer>

    ## Receive storage event notifications [#receive-storage-event-notifications-7]

    A storage event notification returns the [StorageEvent](/en/api-reference/api-ref/signaling#configstorageeventpropsag_platform) data structure, which includes the [RTM\_STORAGE\_EVENT\_TYPE](/en/api-reference/api-ref/signaling#enumvstorageeventtypepropsag_platform) parameter.

    To receive storage event notifications, implement an event listener. See [event listeners](/en/api-reference/api-ref/signaling#event-listeners) for details. You only receive user metadata update notifications for users that you have subscribed to.

    #### Event notification mode [#event-notification-mode-7]

    Currently, Signaling only supports the full data update mode. This means that when a user's metadata is updated, the `data` field in the event notification contains all the metadata of the user.

    ## Version control [#version-control-7]

    Signaling integrates compare-and-set (CAS) version control to manage metadata updates. CAS is a concurrency control mechanism to ensure that updates to a shared resource occur only if the resource is in an expected state. The mechanism works as follows:

    1. The client reads the current version of a data item.
    2. Before making an update, the client compares the current version with the last read version number.
    3. If the versions match, the client proceeds with the update and increments the version number. If they do not match, the update is aborted.

    CAS version control is useful in use-cases that require concurrency management. For instance, consider a dating application where only one user may engage in a chat session with a host. When multiple users attempt to join, only the first request is successful.

    The CAS version control feature provides two independent version control parameters. Set one or more of these values according to the needs of your business use-case:

    * `majorRevision` parameter in the `setMajorRevision` method: Enable version number verification of the entire set of user metadata.

    * `revision` parameter of a `MetadataItem`: Enable version number verification of a single metadata item.

    When setting user metadata, or a single user metadata item, use the revision attribute to enable or disable version control as follows:

    * To disable CAS verification, use the default value of `-1` for the `revision` parameter.

    * To enable CAS verification, set the `majorRevision` or the `revision` parameter to a positive integer. The SDK updates the corresponding value after successfully verifying the revision number. If the specified revision number does not match the latest revision number in the database, the SDK returns an error.

    The following code snippet demonstrates how to employ `majorRevision` and `revision` for updating user metadata and metadata items:

    ```csharp
    var metadata = new RtmMetadata();
    metadata.majorRevision = 174298270;

    // Define the metadata item
    var metadataItem = new MetadataItem()
    {
        key = "Age",
        value = "45",
        revision = 174298100,
    };

    // Add the metadata item to the metadata object
    metadata.metadataItems = new MetadataItem[] { metadataItem };
    metadata.metadataItemsSize = 1;

    // Configure metadata options
    var options = new MetadataOptions()
    {
        recordUserId = true,
        recordTs = true
    };

    // Update user metadata and handle potential errors
    var (status, response) = await rtmClient.GetStorage().UpdateUserMetadataAsync("Tony", metadata, options);
    if (status.Error)
    {
        Debug.Log(string.Format("{0} failed, ErrorCode: {1}, due to: {2}", status.Operation, status.ErrorCode, status.Reason));
    }
    else
    {
        Debug.Log(string.Format("Update user: {0} metadata success! ", response.UserId));
    }
    ```

    In this example, CAS verification for user metadata and metadata items is enabled by setting `majorRevision` and `revision` parameters to positive integers. Upon receiving the update call request, Signaling first verifies the provided major revision number against the latest value in the database. If there's a mismatch, it returns an error; if the values match, Signaling verifies the `revision` number for each metadata item using a similar logic.

    <CalloutContainer type="info">
      <CalloutDescription>
        When using version control, monitor `OnStorageEvent` notifications to retrieve updated values for `majorRevision` and `revision` to ensure that the latest revision values are used for subsequent operations.
      </CalloutDescription>
    </CalloutContainer>

    ## Reference [#reference-7]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    ### API reference [#api-reference-7]

    * [`SetUserMetaDataAsync`](/en/api-reference/api-ref/signaling#storagesetuserpropsag_platform)

    * [`GetUserMetadataAsync`](/en/api-reference/api-ref/signaling#storagegetuserpropsag_platform)

    * [`RemoveUserMetadataAsync`](/en/api-reference/api-ref/signaling#storageremoveuserpropsag_platform)

    * [`UpdateUserMetadataAsync`](/en/api-reference/api-ref/signaling#storageupdateuserpropsag_platform)

    * [Metadata](/en/api-reference/api-ref/signaling#storagemetadatapropsag_platform)

    * [Event listeners](/en/api-reference/api-ref/signaling#event-listeners)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>
</_PlatformTabsGroup>
