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
allowedCamerasbefore 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.ymlundercamera_groupswith camera lists, icons, and order values, validated byfrigate/config/config.pyandweb/src/types/frigateConfig.ts - UI Selection: The
CameraGroupSelectorcomponent filters available groups by permissions and persists selection viauseUserPersistedOverlayState - URL Handling:
useSearchEffectinLive.tsxparses thegroupquery parameter for direct linking to specific views - Rendering:
LiveDashboardView.tsxfilters the camera array based on group membership and renders either individual streams or the birdseye composite view - Security:
useAllowedCamerasensures users only see cameras and groups they have permission to access - Settings: Per-group streaming configurations are managed through
StreamingSettingsProviderand stored inAllGroupsStreamingSettings
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →