# Permission Requests

Build custom UI for permission requests - allowing participants to request audio/video/screen-share permissions, and moderators to accept/reject them.

## Best Practices

- Use `useRequestPermission` hook for sending requests - it handles permission checking and request state.
- Check `canRequestPermission` before showing request buttons.
- Listen to `call.permission_request` event to receive incoming requests.
- Use `call.grantPermissions()` to accept, `call.revokePermissions()` to reject.
- Only users with `UPDATE_CALL_PERMISSIONS` capability can approve/reject requests.

> **Note:** The built-in [`ToggleVideoPublishingButton`](https://getstream.io/video/docs/react/v2/ui-components/call/call-controls/#togglevideopublishingbutton), [`ToggleAudioPublishingButton`](https://getstream.io/video/docs/react/v2/ui-components/call/call-controls/#toggleaudiopublishingbutton), [`ScreenShareButton`](https://getstream.io/video/docs/react/v2/ui-components/call/call-controls/#screensharebutton), and [`PermissionRequests`](https://getstream.io/video/docs/react/v2/ui-components/utility/permission-requests/) components handle this automatically. Only follow this guide for custom UI.

## Start point

Let's start with a simple call UI where we create an `audio_room` call.

```tsx
import {
  StreamVideo,
  StreamCall,
  SpeakerLayout,
  CallControls,
  useCall,
  StreamTheme,
} from "@stream-io/video-react-sdk";
import "@stream-io/video-react-sdk/dist/css/styles.css";

export default function App() {
  // create client and call

  return (
    <StreamTheme>
      <StreamVideo client={client}>
        <StreamCall call={call}>
          <MyCallUI />
        </StreamCall>
      </StreamVideo>
    </StreamTheme>
  );
}

const MyCallUI = () => {
  const call = useCall();
  return (
    <div>
      <MyPermissionRequestNotifications />
      <SpeakerLayout />
      <CallControls />
      <MyPermissionRequests />
    </div>
  );
};

const MyPermissionRequests = () => {};

const MyPermissionRequestNotifications = () => {};
```

>
> **Note:** Permission requests are only relevant for [call types](https://getstream.io/video/docs/react/v2/guides/configuring-call-types/) that have roles where a participant can't publish their audio/video/screen-share by default.
>

This guide shows how to implement `PermissionRequests` to send permission requests and `PermissionRequestNotifications` to receive them.

To follow along with the guide, don't forget to turn off backstage mode so others can join:

```ts
let call: Call;

await call.goLive();
```

## Request permission

In this step we will implement the `MyPermissionRequests` and the `MyPermissionRequestButton` components to send permission requests.

For readability the code snippet only contains the `MyPermissionRequests` and the `MyPermissionRequestButton` implementation, the [full example](#finished-example) is available at the end of the guide.

```tsx
import {
  useRequestPermission,
  useCallStateHooks,
  OwnCapability,
} from "@stream-io/video-react-sdk";

const MyPermissionRequestButton = ({ children, capability }) => {
  const {
    requestPermission,
    hasPermission,
    canRequestPermission,
    isAwaitingPermission,
  } = useRequestPermission(capability);

  if (hasPermission || !canRequestPermission) return null;

  return (
    <button onClick={requestPermission} disabled={isAwaitingPermission}>
      {children}
    </button>
  );
};

const MyPermissionRequests = () => {
  const call = useCall();
  const { useHasPermissions } = useCallStateHooks();
  const canSendAudio = useHasPermissions(OwnCapability.SEND_AUDIO);
  const canSendVideo = useHasPermissions(OwnCapability.SEND_VIDEO);
  const canShareScreen = useHasPermissions(OwnCapability.SCREENSHARE);

  if (!call) return null;

  return (
    <div>
      <MyPermissionRequestButton capability={OwnCapability.SEND_AUDIO}>
        Request audio permission
      </MyPermissionRequestButton>
      <MyPermissionRequestButton capability={OwnCapability.SEND_VIDEO}>
        Request video permission
      </MyPermissionRequestButton>
      <MyPermissionRequestButton capability={OwnCapability.SCREENSHARE}>
        Request screen share permission
      </MyPermissionRequestButton>
      {canSendAudio ? "Allowed to send audio" : "Not allowed to send audio"}
      {canSendVideo ? "Allowed to send video" : "Not allowed to send video"}
      {canShareScreen
        ? "Allowed to share screen"
        : "Not allowed to share screen"}
    </div>
  );
};
```

Let's unpack the above code snippet:

- We only display the request button if the user is allowed to request the specific capability and doesn't already have that permission. For more information, check out the [permissions guide](https://getstream.io/video/docs/react/v2/guides/permissions-and-moderation/).
- To send the request, we are using the `requestPermission` function (wrapper around `requestPermissions` method of the `call` instance) which comes from the `useRequestPermission` hook.
- We use the `useHasPermissions` hook to be notified when the request was approved. Alternatively you can subscribe to the [`call.permissions_updated` event](https://getstream.io/video/docs/react/v2/guides/events/).

## Receive permission requests

In this step we will implement the `MyPermissionRequestNotifications` component to receive permission requests.

For readability, the code snippet only contains the `MyPermissionRequestNotifications` implementation, the [full example](#finished-example) is available at the end of the guide.

```tsx
const MyPermissionRequestNotifications = () => {
  const call = useCall();

  const { useLocalParticipant, useHasPermissions } = useCallStateHooks();
  const localParticipant = useLocalParticipant();
  const canUpdateCallPermissions = useHasPermissions(
    OwnCapability.UPDATE_CALL_PERMISSIONS,
  );

  const [permissionRequests, setPermissionRequests] = useState([]);
  useEffect(() => {
    if (!call || !canUpdateCallPermissions) return;
    return call.on("call.permission_request", (event) => {
      // ignore own requests
      if (event.user.id !== localParticipant?.userId) {
        setPermissionRequests((requests) => [...requests, event]);
      }
    });
  }, [call, canUpdateCallPermissions, localParticipant]);

  if (!call || permissionRequests.length === 0) return null;

  const answerRequest = async (answer, request) => {
    if (answer === "accept") {
      await call.grantPermissions(request.user.id, request.permissions);
    } else {
      await call.revokePermissions(request.user.id, request.permissions);
    }
    setPermissionRequests((requests) => requests.filter((r) => r !== request));
  };

  return (
    <div>
      {permissionRequests.map((request) => (
        <div>
          New request from {request.user.id} to publish {request.permissions}
          <button onClick={() => answerRequest("accept", request)}>
            Accept
          </button>
          <button onClick={() => answerRequest("reject", request)}>
            Reject
          </button>
        </div>
      ))}
    </div>
  );
};
```

Let's unpack the above code snippet:

- We only show the permission requests to users that have the necessary capability to accept/reject: `call.permissionsContext.hasPermission(OwnCapability.UPDATE_CALL_PERMISSIONS)` (or the `useHasPermissions` hook)
- We subscribe to listen to `'call.permission_request'` WS event to update the list of requests
- A permission request can be accepted with the `grantPermissions` method of the `Call` instance
- A permission request can be rejected with the `revokePermissions` method of the `Call` instance. This method can also be used to revoke permissions later in the call.

## Finished example

Here is the full example:

```tsx
import { useEffect, useState } from "react";
import {
  CallControls,
  SpeakerLayout,
  OwnCapability,
  PermissionRequestEvent,
  StreamCall,
  StreamTheme,
  StreamVideo,
  StreamVideoEvent,
  useCall,
  useCallStateHooks,
  useRequestPermission,
} from "@stream-io/video-react-sdk";
import "@stream-io/video-react-sdk/dist/css/styles.css";

export default function App() {
  // create client and call

  return (
    <StreamTheme>
      <StreamVideo client={client}>
        <StreamCall call={call}>
          <MyCallUI />
        </StreamCall>
      </StreamVideo>
    </StreamTheme>
  );
}

const MyCallUI = () => {
  const call = useCall();
  return (
    <div>
      <MyPermissionRequestNotifications />
      <SpeakerLayout />
      <CallControls />
      <MyPermissionRequests />
    </div>
  );
};

const MyPermissionRequestButton = ({ children, capability }) => {
  const {
    requestPermission,
    hasPermission,
    canRequestPermission,
    isAwaitingPermission,
  } = useRequestPermission(capability);

  if (hasPermission || !canRequestPermission) return null;

  return (
    <button disabled={isAwaitingPermission} onClick={requestPermission}>
      {children}
    </button>
  );
};

const MyPermissionRequests = () => {
  const call = useCall();
  const { useHasPermissions } = useCallStateHooks();
  const canSendAudio = useHasPermissions(OwnCapability.SEND_AUDIO);
  const canSendVideo = useHasPermissions(OwnCapability.SEND_VIDEO);
  const canShareScreen = useHasPermissions(OwnCapability.SCREENSHARE);

  if (!call) return null;

  return (
    <div>
      <MyPermissionRequestButton capability={OwnCapability.SEND_AUDIO}>
        Request audio permission
      </MyPermissionRequestButton>
      <MyPermissionRequestButton capability={OwnCapability.SEND_VIDEO}>
        Request video permission
      </MyPermissionRequestButton>
      <MyPermissionRequestButton capability={OwnCapability.SCREENSHARE}>
        Request screen share permission
      </MyPermissionRequestButton>
      {canSendAudio ? "Allowed to send audio" : "Not allowed to send audio"}
      {canSendVideo ? "Allowed to send video" : "Not allowed to send video"}
      {canShareScreen
        ? "Allowed to share screen"
        : "Not allowed to share screen"}
    </div>
  );
};

const MyPermissionRequestNotifications = () => {
  const call = useCall();

  const { useLocalParticipant, useHasPermissions } = useCallStateHooks();
  const localParticipant = useLocalParticipant();
  const canUpdateCallPermissions = useHasPermissions(
    OwnCapability.UPDATE_CALL_PERMISSIONS,
  );

  const [permissionRequests, setPermissionRequests] = useState([]);
  useEffect(() => {
    if (!call || !canUpdateCallPermissions) return;
    return call.on("call.permission_request", (event) => {
      // ignore own requests
      if (event.user.id !== localParticipant?.userId) {
        setPermissionRequests((requests) => [...requests, event]);
      }
    });
  }, [call, canUpdateCallPermissions, localParticipant]);

  if (!call || permissionRequests.length === 0) return null;

  const answerRequest = async (answer, request) => {
    if (answer === "accept") {
      await call.grantPermissions(request.user.id, request.permissions);
    } else {
      await call.revokePermissions(request.user.id, request.permissions);
    }
    setPermissionRequests((requests) => requests.filter((r) => r !== request));
  };

  return (
    <div>
      {permissionRequests.map((request) => (
        <div>
          New request from {request.user.id} to publish {request.permissions}
          <button onClick={() => answerRequest("accept", request)}>
            Accept
          </button>
          <button onClick={() => answerRequest("reject", request)}>
            Reject
          </button>
        </div>
      ))}
    </div>
  );
};
```

---

For the most recent version of this documentation, visit [https://getstream.io/video/docs/react/v2/ui-cookbook/permission-requests/](https://getstream.io/video/docs/react/v2/ui-cookbook/permission-requests/).