How Frigate Handles Camera Groups and Multi-Camera Views: Configuration to UI Rendering

Frigate organizes cameras into named groups via YAML configuration and renders multi-camera views through React components that filter, persist, and display grouped streams based on user permissions.

Frigate's video surveillance platform allows users to organize cameras into logical groups that appear as unified views in the web interface. The primary keyword configuration lives under the camera_groups key in your config.yml, while the frontend implementation spans React hooks, context providers, and dashboard components. Understanding this pipeline—from backend validation to permission-aware rendering—enables you to build efficient multi-camera monitoring layouts.

Defining Camera Groups in Configuration

Camera groups are declared in the YAML configuration under the top-level key camera_groups. Each entry maps a group name to a CameraGroupConfig object that specifies which cameras belong together, what icon represents the group, and how it should be ordered in the UI selector.

camera_groups:
  front_door:
    cameras: [front_left, front_right]
    icon: "door_front"
    order: 1
  backyard:
    cameras: [back_left, back_right]
    icon: "tree"
    order: 2

Schema Validation on Backend and Frontend

The configuration schema is validated on both sides of the stack. In frigate/config/config.py, the FrigateConfig class declares camera_groups: Dict[str, CameraGroupConfig] using Pydantic models to enforce structure at load time. The TypeScript type that mirrors this structure lives in web/src/types/frigateConfig.ts, specifically lines 342-346, where CameraGroupConfig defines the interface for the React frontend.

Selecting and Persisting Groups in the UI

The CameraGroupSelector component in web/src/components/filter/CameraGroupSelector.tsx builds the dropdown interface that lets users switch between groups. It reads the full configuration via SWR (useSWR<FrigateConfig>("config")) and uses useMemo to build an ordered list of available groups.

The component handles permission filtering by checking hasFullCameraAccess and allowedCameras before displaying a group option. If a user lacks access to any camera within a group, that group is filtered from the selector.

State Persistence Across Sessions

The selector persists the chosen group using useUserPersistedOverlayState("cameraGroup", "default"). This hook stores the selection in user-scoped localStorage, ensuring that returning users see their preferred camera group without reselecting it manually.

Loading Groups from URL Parameters

When users navigate directly to a specific group view, Frigate parses the group query parameter from the URL. In web/src/pages/Live.tsx (lines 29-34), the useSearchEffect hook checks for the parameter and validates it against the configuration:

useSearchEffect("group", (cameraGroup) => {
  if (config && cameraGroup && loaded) {
    const group = config.camera_groups[cameraGroup];
    if (group) {
      setCameraGroup(cameraGroup);
      return false;   // keep URL clean – UI handles icon change
    }
    return true;
  }
  return false;
});

This allows deep linking to specific views (e.g., /live?group=backyard) while maintaining clean URLs once the state is loaded.

Filtering Cameras for Multi-Camera Views

The LiveDashboardView component in web/src/views/live/LiveDashboardView.tsx handles the actual rendering logic for multi-camera layouts. It receives the selected cameraGroup and filters the master camera list to include only those specified in the group configuration.

Lines 17-21 demonstrate the filtering logic:

return cameras
  .map((cam) => cam.name)
  .filter((cam) => config.camera_groups[cameraGroup]?.cameras.includes(cam))
  .join(",");

If the selected group is "default", the view falls back to displaying all cameras with ui.dashboard enabled. For custom groups, it extracts the camera names from config.camera_groups[group].cameras and further filters them against the user's allowedCameras list.

Birdseye Global View

When a group contains the special pseudo-camera birdseye, Frigate renders a unified composite view instead of individual streams. The Live page checks for this condition using an includesBirdseye memo and conditionally renders LiveBirdseyeView when selectedCameraName === "birdseye". This provides a global multi-camera perspective without consuming multiple video decoder resources.

Access Control and Permission Filtering

Frigate enforces camera access permissions at multiple points in the group rendering pipeline. The useAllowedCameras hook in web/src/hooks/use-allowed-cameras.ts returns the subset of cameras the current user is authorized to view based on their role.

Both the CameraGroupSelector and LiveDashboardView apply this filter:

  • Selector level: Groups containing no accessible cameras are hidden entirely
  • Dashboard level: The final camera list intersects with allowedCameras before rendering

This ensures that sensitive camera feeds remain invisible even if a user manually constructs a URL with a restricted group name.

Per-Group Streaming Settings

Frigate stores group-specific streaming preferences in AllGroupsStreamingSettings (defined in web/src/types/frigateConfig.ts lines 81-83). The StreamingSettingsProvider context manages these parameters, allowing each group to maintain independent settings for resolution, audio enablement, and compatibility mode.

When the user switches groups, the provider updates the underlying stream parameters for every camera in the group, ensuring that performance-intensive settings like high-resolution birdseye views don't affect standard single-camera streams.

Summary

  • Configuration: Define groups in config.yml under camera_groups with camera lists, icons, and order values, validated by frigate/config/config.py and web/src/types/frigateConfig.ts
  • UI Selection: The CameraGroupSelector component filters available groups by permissions and persists selection via useUserPersistedOverlayState
  • URL Handling: useSearchEffect in Live.tsx parses the group query parameter for direct linking to specific views
  • Rendering: LiveDashboardView.tsx filters the camera array based on group membership and renders either individual streams or the birdseye composite view
  • Security: useAllowedCameras ensures users only see cameras and groups they have permission to access
  • Settings: Per-group streaming configurations are managed through StreamingSettingsProvider and stored in AllGroupsStreamingSettings

Frequently Asked Questions

How do I create a camera group that shows only specific cameras?

Define the group in your config.yml under the camera_groups key with a cameras array containing the camera IDs you want included. For example, create a "front_yard" group listing only your porch and driveway cameras. The UI will automatically populate the selector with this group once Frigate reloads the configuration.

What is the difference between a regular camera group and the birdseye view?

A regular camera group displays individual camera streams side-by-side in a grid or list layout, while the birdseye view composites all cameras into a single unified stream. Birdseye appears when you select a group containing the special birdseye pseudo-camera, handled in Live.tsx by rendering LiveBirdseyeView instead of individual camera components.

Why does my camera group not appear in the dropdown selector?

The CameraGroupSelector filters out groups where you lack camera access permissions. Verify that your user role includes access to at least one camera in the group via the useAllowedCameras logic. Additionally, ensure your YAML syntax is valid and the group is properly nested under camera_groups in the configuration file.

Can different camera groups use different streaming quality settings?

Yes. Frigate stores streaming preferences per-group in AllGroupsStreamingSettings managed by the StreamingSettingsProvider context. When you switch groups, the UI applies that group's specific resolution, audio, and compatibility settings to all cameras in the view, allowing high-quality settings for important groups and lower bandwidth options for others.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →