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

# Permissions

> Control what participants can send, receive, and administer in a Daily call, including knock-to-join waiting rooms.

Daily's permissions system lets you control exactly what each participant is allowed to do in a call: which media they can publish, which streams they can receive, and which administrative actions they can perform. Permissions can be set at join time via a [meeting token](/reference/rest-api/meeting-tokens/create-meeting-token) or updated dynamically at runtime.

## DailyParticipantPermissions

Every participant object exposes a `permissions` field of type `DailyParticipantPermissions`:

```typescript theme={null}
interface DailyParticipantPermissions {
  hasPresence: boolean;
  canSend: Set<'video' | 'audio' | 'screenVideo' | 'screenAudio' | 'customVideo' | 'customAudio'> | boolean;
  canReceive: DailyParticipantCanReceivePermission;
  canAdmin: Set<'participants' | 'streaming' | 'transcription'> | boolean;
}
```

<ResponseField name="hasPresence" type="boolean">
  When `false`, the participant is hidden from all other participants. They do not appear in `participants()` for others, cannot be seen, and their events are not broadcast. Useful for silent observers or bots. Hidden participants require SFU topology.
</ResponseField>

<ResponseField name="canSend" type="Set<string> | boolean">
  Controls which media tracks the participant may publish.

  <Expandable title="allowed values">
    <ResponseField name="video" type="string">Camera video track.</ResponseField>
    <ResponseField name="audio" type="string">Microphone audio track.</ResponseField>
    <ResponseField name="screenVideo" type="string">Screen share video.</ResponseField>
    <ResponseField name="screenAudio" type="string">Screen share audio.</ResponseField>
    <ResponseField name="customVideo" type="string">Any custom video track started with `startCustomTrack()`.</ResponseField>
    <ResponseField name="customAudio" type="string">Any custom audio track started with `startCustomTrack()`.</ResponseField>
  </Expandable>

  Pass `true` to allow all, `false` to block all, or a `Set` of specific values.
</ResponseField>

<ResponseField name="canReceive" type="DailyParticipantCanReceivePermission">
  Controls which participants' media streams this participant may subscribe to.

  <Expandable title="properties">
    <ResponseField name="base" type="DailyParticipantCanReceiveMediaPermissionFull | boolean" required>
      Default receive permission that applies to all participants unless overridden. `true` allows all media from all participants, `false` blocks all. Or provide a granular object:

      ```typescript theme={null}
      {
        video: boolean;
        audio: boolean;
        screenVideo: boolean;
        screenAudio: boolean;
        customVideo: { '*': boolean; [trackName: string]: boolean };
        customAudio: { '*': boolean; [trackName: string]: boolean };
      }
      ```
    </ResponseField>

    <ResponseField name="byUserId" type="Record<string, DailyParticipantCanReceiveMediaPermissionPartial | boolean>">
      Per-user overrides keyed by `user_id`. Values are partial permission objects — only the fields you specify override the base.
    </ResponseField>

    <ResponseField name="byParticipantId" type="Record<string, DailyParticipantCanReceiveMediaPermissionPartial | boolean>">
      Per-session overrides keyed by `session_id`. Takes priority over `byUserId` and `base`.
    </ResponseField>
  </Expandable>

  Override resolution order (highest to lowest): `byParticipantId` → `byUserId` → `base`.
</ResponseField>

<ResponseField name="canAdmin" type="Set<string> | boolean">
  Controls which administrative capabilities the participant holds.

  <Expandable title="allowed values">
    <ResponseField name="participants" type="string">Can mute, eject, and update permissions of other participants. Can also admit waiting participants.</ResponseField>
    <ResponseField name="streaming" type="string">Can start and stop cloud recordings and live streams.</ResponseField>
    <ResponseField name="transcription" type="string">Can start and stop transcription.</ResponseField>
  </Expandable>

  Pass `true` to grant all admin rights, `false` to grant none, or a `Set` of specific values.
</ResponseField>

## Reading permissions

Use [`usePermissions`](/reference/daily-react/use-permissions) to reactively read the local participant's permissions. It returns convenience booleans for each permission type:

```jsx theme={null}
import { usePermissions } from '@daily-co/daily-react';

function MediaControls() {
  const {
    canSendAudio,
    canSendVideo,
    canSendScreenVideo,
    canAdminParticipants,
    canAdminTranscription,
  } = usePermissions();

  return (
    <div>
      {canSendAudio && <MicButton />}
      {canSendVideo && <CameraButton />}
      {canSendScreenVideo && <ScreenShareButton />}
      {canAdminTranscription && <TranscriptionButton />}
    </div>
  );
}
```

To read another participant's permissions, pass their `sessionId` to `usePermissions`:

```jsx theme={null}
const { canSendAudio } = usePermissions(participantSessionId);
```

## Updating permissions at runtime

Use [`useDaily`](/reference/daily-react/use-daily) to get the call object and call `updateParticipant()`. The caller must themselves have `canAdmin: 'participants'` (or `canAdmin: true`).

```jsx theme={null}
import { useDaily } from '@daily-co/daily-react';

function ParticipantControls({ sessionId }) {
  const daily = useDaily();

  const muteParticipant = () => {
    daily.updateParticipant(sessionId, {
      updatePermissions: {
        canSend: new Set(['video']), // video only, no audio
      },
    });
  };

  const restoreParticipant = () => {
    daily.updateParticipant(sessionId, {
      updatePermissions: {
        canSend: new Set(['audio', 'video']),
      },
    });
  };

  return (
    <>
      <button onClick={muteParticipant}>Mute</button>
      <button onClick={restoreParticipant}>Unmute</button>
    </>
  );
}
```

The `updatePermissions` object is partial — only the fields you provide are changed:

```typescript theme={null}
type DailyParticipantPermissionsUpdate = {
  hasPresence?: boolean;
  canSend?: Array<string> | Set<string> | boolean;
  canReceive?: Partial<DailyParticipantCanReceivePermission>;
  canAdmin?: Array<string> | Set<string> | boolean;
};
```

### Restricting what a participant can send

Restrict a participant to audio-only (no camera or screen share):

```jsx theme={null}
daily.updateParticipant(sessionId, {
  updatePermissions: {
    canSend: new Set(['audio']),
  },
});
```

### Restricting what a participant can receive

Allow a participant to receive audio from everyone but video only from a specific user:

```jsx theme={null}
daily.updateParticipant(sessionId, {
  updatePermissions: {
    canReceive: {
      base: {
        video: false,
        audio: true,
        screenVideo: false,
        screenAudio: false,
        customVideo: { '*': false },
        customAudio: { '*': false },
      },
      byUserId: {
        'presenter-user-id': { video: true },
      },
    },
  },
});
```

### Granting admin permissions

Grant a participant transcription admin rights so they can start and stop transcription:

```jsx theme={null}
daily.updateParticipant(sessionId, {
  updatePermissions: {
    canAdmin: new Set(['transcription']),
  },
});
```

## Waiting room (knock-to-join)

When a room has knocking enabled, participants who attempt to join land in a waiting room until a host admits or denies them. Use [`useWaitingParticipants`](/reference/daily-react/use-waiting-participants) to manage the waiting room from the host side.

### Host: managing waiting participants

`useWaitingParticipants` returns `waitingParticipants`, `grantAccess`, and `denyAccess`. Pass callbacks to react to participants entering or leaving the lobby:

```jsx theme={null}
import { useCallback } from 'react';
import { useWaitingParticipants } from '@daily-co/daily-react';

function WaitingRoom() {
  const onWaitingParticipantAdded = useCallback((event) => {
    console.log(`${event.participant.name} is waiting`);
  }, []);

  const { waitingParticipants, grantAccess, denyAccess } = useWaitingParticipants({
    onWaitingParticipantAdded,
  });

  return (
    <ul>
      {waitingParticipants.map((p) => (
        <li key={p.id}>
          {p.name}
          <button onClick={() => grantAccess(p.id)}>Admit</button>
          <button onClick={() => denyAccess(p.id)}>Deny</button>
        </li>
      ))}
    </ul>
  );
}
```

To admit or deny all waiting participants at once, pass `'*'`:

```jsx theme={null}
grantAccess('*'); // admit everyone
denyAccess('*');  // deny everyone
```

### Guest: checking access state

Use [`useDaily`](/reference/daily-react/use-daily) and [`useDailyEvent`](/reference/daily-react/use-daily-event) to track the local participant's access state:

```jsx theme={null}
import { useState, useCallback } from 'react';
import { useDaily, useDailyEvent } from '@daily-co/daily-react';

function AccessStateHandler() {
  const daily = useDaily();
  const [screen, setScreen] = useState('joining');

  useDailyEvent('access-state-updated', useCallback(({ access }) => {
    if (typeof access === 'object') {
      if (access.level === 'lobby') setScreen('waiting');
      else if (access.level === 'full') setScreen('call');
      else if (access.level === 'none') setScreen('denied');
    }
  }, []));

  if (screen === 'waiting') return <p>Waiting for the host to admit you…</p>;
  if (screen === 'denied') return <p>Access denied.</p>;
  return null;
}
```

## Example: setting up a waiting room

<Steps>
  <Step title="Enable knocking on the room">
    Create or configure a Daily room with `enable_knocking: true` via the Daily REST API or dashboard.
  </Step>

  <Step title="Listen for waiting participants (host)">
    ```jsx theme={null}
    import { useCallback } from 'react';
    import { useWaitingParticipants } from '@daily-co/daily-react';

    function KnockingManager() {
      const onWaitingParticipantAdded = useCallback(({ participant }) => {
        showKnockPrompt(participant);
      }, []);

      const onWaitingParticipantRemoved = useCallback(({ participant }) => {
        dismissKnockPrompt(participant.id);
      }, []);

      const { waitingParticipants, grantAccess, denyAccess } = useWaitingParticipants({
        onWaitingParticipantAdded,
        onWaitingParticipantRemoved,
      });

      return waitingParticipants.map((p) => (
        <KnockPrompt
          key={p.id}
          participant={p}
          onAdmit={() => grantAccess(p.id)}
          onDeny={() => denyAccess(p.id)}
        />
      ));
    }
    ```
  </Step>

  <Step title="Handle access state changes (guest)">
    ```jsx theme={null}
    import { useState, useCallback } from 'react';
    import { useDailyEvent } from '@daily-co/daily-react';

    function LobbyScreen() {
      const [accessLevel, setAccessLevel] = useState(null);

      useDailyEvent('access-state-updated', useCallback(({ access }) => {
        if (typeof access === 'object') setAccessLevel(access.level);
      }, []));

      if (accessLevel === 'lobby') return <p>Waiting for the host…</p>;
      if (accessLevel === 'full') return <CallView />;
      if (accessLevel === 'none') return <p>You were not admitted.</p>;
      return null;
    }
    ```
  </Step>
</Steps>

<Note>
  Participants with `hasPresence: false` require SFU network topology. Daily automatically switches to SFU mode when hidden participants are present, but your room must support SFU.
</Note>
