20 KiB
| task | slug | created | status |
|---|---|---|---|
| UI Configuration using react-jsonschema-form | ui-config-rjsf | 2026-01-21 | planning |
UI Configuration using react-jsonschema-form
Overview
Implement a comprehensive configuration UI for Frigate NVR using react-jsonschema-form (RJSF), driven by the existing Pydantic configuration schema. The UI should allow users to configure Frigate through a web interface instead of manually editing YAML files, with proper validation, reusable components, and clear visual indicators for global vs camera-level settings.
Research Findings
Key Components
| Component | Location | Purpose |
|---|---|---|
| FrigateConfig | frigate/config/config.py | Root Pydantic model defining complete Frigate configuration |
| CameraConfig | frigate/config/camera/camera.py | Camera-level configuration model |
| FrigateBaseModel | frigate/config/base.py | Base model with extra="forbid" validation |
| Config API | frigate/api/app.py | PUT /config/set endpoint for saving config |
| Schema Endpoint | frigate/api/app.py | GET /config/schema.json - exposes Pydantic schema |
| Settings Page | web/src/pages/Settings.tsx | Main settings page with sidebar navigation |
| Form Components | web/src/components/ui/form.tsx | Existing react-hook-form based form components |
Architecture
The Frigate configuration system is structured as follows:
-
Pydantic Models: All configuration is defined using Pydantic v2 models in
frigate/config/. TheFrigateConfigclass is the root model. -
Schema Generation: Pydantic automatically generates JSON Schema via
model_json_schema(). The schema is already exposed at/api/config/schema.jsonand used by the ConfigEditor (monaco-yaml) for validation. -
Config API: The
/config/setendpoint accepts configuration updates in two formats:- Query string parameters (e.g.,
?cameras.front.detect.enabled=True) - JSON body via
config_datafield inAppConfigSetBody
The JSON body is preferred.
- Query string parameters (e.g.,
-
Frontend Stack:
- React 18 with TypeScript
- Radix UI primitives + Tailwind CSS (shadcn/ui patterns)
- react-hook-form + zod for form validation
- SWR for data fetching
- react-i18next for translations
- axios for API calls
Data Flow
- Config Loading:
useSWR("config")fetches current config from/api/config - Schema Loading: JSON Schema available at
/api/config/schema.json - Config Updates: PUT to
/config/setwith JSON body - Real-time Updates: Some settings support
requires_restart: 0for live updates via WebSocket pub/sub
Reusable Config Sections (Global + Camera Level)
Based on schema analysis, these sections appear at BOTH global and camera levels using the same Pydantic model:
| Section | Model | Notes |
|---|---|---|
| audio | AudioConfig | Full model reuse |
| detect | DetectConfig | Full model reuse, nested StationaryConfig |
| live | CameraLiveConfig | Full model reuse |
| motion | MotionConfig | Full model reuse |
| notifications | NotificationConfig | Full model reuse |
| objects | ObjectConfig | Full model reuse, nested FilterConfig, GenAIObjectConfig |
| record | RecordConfig | Full model reuse, nested EventsConfig, RetainConfig |
| review | ReviewConfig | Full model reuse, nested ReviewAlertsConfig, ReviewDetectionsConfig |
| snapshots | SnapshotsConfig | Full model reuse, nested SnapshotsRetainConfig |
| timestamp_style | TimestampStyleConfig | Full model reuse, nested ColorConfig |
Different models at global vs camera level (require conditional handling):
| Section | Global Model | Camera Model | Notes |
|---|---|---|---|
| audio_transcription | AudioTranscriptionConfig | CameraAudioTranscriptionConfig | Camera has subset |
| birdseye | BirdseyeConfig | BirdseyeCameraConfig | Different fields |
| face_recognition | FaceRecognitionConfig | CameraFaceRecognitionConfig | Camera has subset |
| ffmpeg | FfmpegConfig | CameraFfmpegConfig | Camera adds inputs[] |
| lpr | LicensePlateRecognitionConfig | CameraLicensePlateRecognitionConfig | Camera has subset |
| semantic_search | SemanticSearchConfig | CameraSemanticSearchConfig | Completely different |
Special Field Types Requiring Custom Widgets
| Type | Examples | Widget Needed |
|---|---|---|
| Enums | BirdseyeModeEnum, RetainModeEnum, RecordQualityEnum | Select dropdown |
| list[str] | objects.track, zones[], required_zones | Tag input / Multi-select |
| Union[str, list[str]] | mask fields | Text or array input |
| dict[str, T] | zones, objects.filters | Key-value editor / Nested form |
| ColorConfig | timestamp_style.color (RGB) | Color picker |
| Coordinates/Masks | zone.coordinates | Polygon editor (existing) |
| Password fields | mqtt.password | Password input with show/hide |
Current UI Patterns
- Settings Navigation: web/src/pages/Settings.tsx uses a sidebar with grouped sections
- Camera Selection: Many views accept
selectedCameraprop for per-camera settings - Form Pattern: Uses react-hook-form with zod schemas (see CameraReviewSettingsView.tsx)
- Save Pattern: Axios PUT to
config/set?key=valuewithrequires_restartflag - Translations: All strings in
web/public/locales/{lang}/views/JSON files
Dependencies
Internal:
- Existing form components: Form, FormField, FormItem, FormLabel, FormControl, FormMessage
- UI primitives: Switch, Select, Input, Checkbox, Slider, Tabs
- Existing hooks: useSWR, useTranslation, useOptimisticState
External (to add):
- @rjsf/core (react-jsonschema-form core)
- @rjsf/utils (utilities)
- @rjsf/validator-ajv8 (JSON Schema validation)
Configuration
The /config/set API expects:
interface AppConfigSetBody {
requires_restart: number; // 0 = live update, 1 = needs restart
update_topic?: string; // For pub/sub notification
config_data?: Record<string, any>; // Bulk config updates
}
Query string format: ?key.path.to.field=value (e.g., ?cameras.front.detect.enabled=True)
Tests
| Test File | Coverage |
|---|---|
| No existing React component tests found | RJSF forms would benefit from testing |
| Pydantic validation tests implicit in config loading | Schema validation ensures correctness |
Implementation Plan
Goal
Users can configure all user-facing Frigate settings through a form-based UI with validation, clear global vs camera-level distinction, and proper handling of advanced settings.
Current State Analysis
- Schema already exposed at
/api/config/schema.json - Settings page structure exists with sidebar navigation
- Form components exist (react-hook-form based)
- No react-jsonschema-form currently installed
- Config set API supports both query strings and JSON body
What We're NOT Doing
- Replacing the raw YAML ConfigEditor (remains as advanced option)
- Changing the Pydantic models structure
- Modifying the config/set API endpoint
- Auto-generating translations (manual translation required)
Prerequisites
- Install @rjsf/core, @rjsf/utils, @rjsf/validator-ajv8
- Create exclusion list and advanced settings list JSON files
Phases
| # | Phase | Status | Plan | Notes |
|---|---|---|---|---|
| 1 | Schema Pipeline & RJSF Setup | ✅ Done | — | Install deps, create schema transformer, set up RJSF theme |
| 2 | Core Reusable Section Components | ✅ Done | — | Create shared components for detect, record, snapshots, etc. |
| 3 | Global Configuration View | ✅ Done | — | Build main config sections (mqtt, auth, database, etc.) |
| 4 | Camera Configuration with Tabs | ✅ Done | — | Multi-camera tabs, override indicators, section reuse |
| 5 | Advanced Settings & Exclusions | ✅ Done | — | Progressive disclosure, contributor-editable lists |
| 6 | Validation & Error Handling | ✅ Done | — | Inline errors, save blocking, API integration |
| 7 | Integration & Polish | ✅ Done | — | Settings page integration, translations, documentation |
Status: ⬜ Not Started → 📋 Planned → 🔄 In Progress → ✅ Done
Phase Details
Phase 1: Schema Pipeline & RJSF Setup
Overview: Install react-jsonschema-form dependencies, create a schema transformation layer to convert Pydantic JSON Schema to RJSF-compatible format with UI customizations.
Changes Required:
-
Install Dependencies
- Add to package.json: @rjsf/core, @rjsf/utils, @rjsf/validator-ajv8
-
Create Schema Transformer
- File:
web/src/lib/config-schema/ - Transform Pydantic schema to RJSF uiSchema
- Handle nested $defs/references
- Apply field ordering
- File:
-
Create Custom RJSF Theme
- File:
web/src/components/config-form/theme/ - Map RJSF templates to existing shadcn/ui components
- Custom widgets for special types (color, coordinates, etc.)
- File:
Success Criteria:
- RJSF renders basic form from schema
- Existing UI component styling preserved
- Schema fetching from /api/config/schema.json works
Phase 2: Core Reusable Section Components
Overview: Create composable section components for config areas that appear at both global and camera levels.
Changes Required:
-
Section Component Architecture
- File:
web/src/components/config-form/sections/ - Create: DetectSection, RecordSection, SnapshotsSection, MotionSection, ObjectsSection, ReviewSection, AudioSection, NotificationsSection, LiveSection, TimestampSection
- File:
-
Each Section Component:
- Accepts
level: "global" | "camera"prop - Accepts
cameraName?: stringfor camera context - Accepts
showOverrideIndicator?: boolean - Uses shared RJSF form with section-specific uiSchema
- Accepts
-
Override Detection Hook
- File:
web/src/hooks/use-config-override.ts - Compare camera value vs global default
- Return override status for visual indicators
- File:
-
Field Ordering and Layout Customization
Requirement: Field ordering and layout within each section must be easily customizable by contributors without requiring deep knowledge of RJSF internals.
Implementation Approach:
- Each reusable section component (DetectSection, RecordSection, etc.) should define its own field ordering and layout configuration
- This can be accomplished as a TypeScript constant within the section component file itself
- The configuration should specify:
- Field display order
- Field grouping (which fields appear together)
- Layout hints (e.g., multiple fields per row, nested groupings)
- Any section-specific uiSchema customizations
Example Structure:
// In DetectSection.tsx or DetectSection.config.ts
export const detectSectionConfig = {
fieldOrder: [
"enabled",
"fps",
"width",
"height",
"max_disappeared",
"stationary",
],
fieldGroups: {
resolution: ["width", "height"],
performance: ["fps", "max_disappeared"],
},
// ... other layout hints
};
Success Criteria:
- Contributors can reorder fields by editing a clear configuration structure
- No need to modify RJSF internals or complex uiSchema objects directly
- Layout changes are localized to single files per section
Success Criteria:
- DetectSection renders identically at global and camera level
- Override indicators show when camera differs from global
- Adding new fields requires editing only section definition
Phase 3: Global Configuration View
Overview: Build the global configuration form for non-camera settings.
Changes Required:
-
Global Config View
- File:
web/src/views/settings/GlobalConfigView.tsx - Sections: MQTT, Auth, Database, Telemetry, TLS, Proxy, Networking, UI, Detectors, Model, GenAI, Classification, Birdseye
- File:
-
Per-Section Subforms
- Each section as collapsible card
- Progressive disclosure for advanced fields
- Individual save buttons per section OR unified save
-
Translations
- File:
web/public/locales/en/views/settings.json - Add keys for all config field labels/descriptions
- File:
Success Criteria:
- All global-only settings configurable
- Proper field grouping and labels
- Validation errors inline
Phase 4: Camera Configuration with Tabs
Overview: Create per-camera configuration with tab navigation and override indicators.
Changes Required:
-
Camera Config View
- File:
web/src/views/settings/CameraConfigView.tsx - Tab per camera
- Uses reusable section components
- File:
-
Override Visual Indicators
- Badge/icon when field overrides global
- "Reset to global default" action
- Color coding (e.g., highlighted border)
-
Camera-Specific Sections
- FFmpeg inputs configuration
- Masks and Zones (link to existing editor)
- ONVIF settings
Success Criteria:
- Switch between cameras via tabs
- Clear visual distinction for overridden settings
- Reset to global default works
Phase 5: Advanced Settings & Exclusions
Overview: Implement progressive disclosure and maintainable exclusion lists.
Changes Required:
-
Exclusion System
- Simple consts in the component for field names
- Filter schema before rendering
- Document exclusion format for contributors
-
Advanced Fields Toggle
- "Show Advanced Settings" switch per section
- Simple consts in the component for advanced field names
- Default collapsed state
Success Criteria:
- Excluded fields never shown in UI
- Advanced fields hidden by default
Phase 6: Validation & Error Handling
Overview: Ensure robust validation and user-friendly error messages.
Changes Required:
-
Client-Side Validation
- ajv8 validator with Pydantic schema
- Custom error messages for common issues
- Real-time validation on blur
-
Server-Side Validation
- Handle 400 responses from /config/set
- Parse Pydantic validation errors
- Map to form fields
-
Save Blocking
- Disable save button when invalid
- Show error count badge
- Scroll to first error on submit attempt
Success Criteria:
- Invalid forms cannot be saved
- Errors shown inline next to fields
- Clear error messages (not technical schema errors)
Phase 7: Integration & Polish
Overview: Integrate into settings page, finalize translations, and document.
Changes Required:
-
Settings Page Integration
- Add new views to settingsGroups in Settings.tsx
- Sidebar navigation updates
- Route configuration
-
Translations
- All field labels and descriptions
- Error messages
- Section headers
-
Documentation
- User-facing docs for UI configuration
-
Testing
- Basic render tests for form components
- Validation behavior tests
- Save/cancel flow tests
Success Criteria:
- Seamless navigation from settings page
- All strings translated
- Documentation complete
Testing Strategy
Project Maturity Level
Active Development - Frigate has extensive test infrastructure but limited frontend tests.
Unit Tests
- Schema transformer functions
- Override detection hook
- Custom widgets
- Coverage target: 70% for new components
Integration/Manual Tests
- Full form render with live schema
- Save/validation flow end-to-end
- Camera tab switching
- Override indicator accuracy
- Mobile responsiveness
Rollback Plan
- All changes are additive - existing ConfigEditor remains functional
- New views can be feature-flagged if needed
- No database migrations required
- No backend changes required (uses existing API)
Component Hierarchy
web/src/
├── components/
│ └── config-form/
│ ├── theme/
│ │ ├── index.ts # RJSF theme export
│ │ ├── templates/ # Base templates (ObjectFieldTemplate, etc.)
│ │ └── widgets/ # Custom widgets (ColorWidget, TagsWidget, etc.)
│ ├── sections/
│ │ ├── DetectSection.tsx # Reusable for global + camera
│ │ ├── RecordSection.tsx
│ │ ├── SnapshotsSection.tsx
│ │ ├── MotionSection.tsx
│ │ ├── ObjectsSection.tsx
│ │ ├── ReviewSection.tsx
│ │ ├── AudioSection.tsx
│ │ ├── NotificationsSection.tsx
│ │ ├── LiveSection.tsx
│ │ └── TimestampSection.tsx
│ └── ConfigForm.tsx # Main form wrapper
├── lib/
│ └── config-schema/
│ ├── index.ts # Schema utilities
│ ├── transformer.ts # Pydantic -> RJSF schema
├── hooks/
│ └── use-config-override.ts # Override detection
└── views/
└── settings/
├── GlobalConfigView.tsx # Global settings form
└── CameraConfigView.tsx # Per-camera tabs form
Key Design Decisions
-
RJSF over custom forms: Leverage schema-driven forms for maintainability and automatic updates when Pydantic models change.
-
Reusable sections via composition: Same component renders at global and camera level, with props controlling context and override indicators.
-
Existing UI primitives: Custom RJSF theme wraps existing shadcn/ui components for visual consistency.
-
Incremental adoption: Existing settings views remain, new RJSF views added alongside.