# Custom Fields Plugin System Documentation
> Note: For the comprehensive technical design, integration guide, and scope, see [docs/custom-fields-plugin-management.md](docs/custom-fields-plugin-management.md). This page focuses on implementation specifics; the linked document is the source of truth for architecture and scope.
## Table of Contents
1. [Overview](#overview)
2. [Architecture](#architecture)
3. [Database Schema](#database-schema)
4. [API Endpoints](#api-endpoints)
5. [Plugin Activation System](#plugin-activation-system)
6. [Frontend Integration](#frontend-integration)
7. [Page Identifiers](#page-identifiers)
8. [Implementation Guidelines](#implementation-guidelines)
9. [Security & Performance](#security--performance)
10. [Usage Examples](#usage-examples)
## Overview
The Custom Fields Plugin System allows administrators to dynamically add custom fields to any form in the admin panel. The system provides:
- Dynamic field creation with multiple field types
- Role and plan-based field visibility
- Field grouping through field sets
- Conditional field logic
- Caching for performance
- Plugin activation/deactivation from admin panel
### Key Features
- **Field Types**: text, textarea, select, multiselect, checkbox, radio, number, date, file
- **Field Validation**: Custom validation rules per field
- **Field Visibility**: Control visibility based on user roles and subscription plans
- **Field Sets**: Group related fields for reusability
- **Caching**: Redis-based caching with 60-second TTL
- **Audit Logging**: Complete audit trail for all operations
## Architecture
### System Components
```
ââ€Å’ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â
ââ€â€š FRONTEND (React) ââ€â€š
ââ€Å“ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â¤
ââ€â€š - Custom Fields UI Components ââ€â€š
ââ€â€š - Plugin Management Page ââ€â€š
ââ€â€š - Dynamic Form Renderer ââ€â€š
ââ€â€š - Field Validation ââ€â€š
ââ€â€Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Ëœ
ââ€â€š HTTP/REST API
ââ€Å’ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€“¼ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â
ââ€â€š BACKEND (Express.js) ââ€â€š
ââ€Å“ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â¤
ââ€â€š - Admin Routes (/admin/custom-fields/*) ââ€â€š
ââ€â€š - Feature Toggle Middleware ââ€â€š
ââ€â€š - Authentication & Authorization ââ€â€š
ââ€â€š - Request Validation ââ€â€š
ââ€â€Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Ëœ
ââ€â€š
ââ€Å’ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€“¼ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â
ââ€â€š NODE_MICROSERVICES ââ€â€š
ââ€Å“ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â¤
ââ€â€š Controllers: ââ€â€š
ââ€â€š - AdminCustomFieldsController ââ€â€š
ââ€â€š ââ€â€š
ââ€â€š Models (Sequelize): ââ€â€š
ââ€â€š - CustomField ââ€â€š
ââ€â€š - CustomFieldOption ââ€â€š
ââ€â€š - CustomFieldAssignment ââ€â€š
ââ€â€š - CustomFieldSet ââ€â€š
ââ€â€š - CustomFieldSetItem ââ€â€š
ââ€â€š - CustomFieldSetAssignment ââ€â€š
ââ€â€š - ConditionalRule ââ€â€š
ââ€â€š ââ€â€š
ââ€â€š Middleware: ââ€â€š
ââ€â€š - featureToggle ââ€â€š
ââ€â€š ââ€â€š
ââ€â€š Services: ââ€â€š
ââ€â€š - Redis Cache ââ€â€š
ââ€â€š - Audit Logger ââ€â€š
ââ€â€Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Ëœ
ââ€â€š
ââ€Å’ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€“¼ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â
ââ€â€š DATABASE (MySQL) ââ€â€š
ââ€Å“ââ€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Â¤
ââ€â€š - custom_fields ââ€â€š
ââ€â€š - custom_field_options ââ€â€š
ââ€â€š - custom_field_assignments ââ€â€š
ââ€â€š - custom_field_sets ââ€â€š
ââ€â€š - custom_field_set_items ââ€â€š
ââ€â€š - custom_field_set_assignments ââ€â€š
ââ€â€š - conditional_rules ââ€â€š
ââ€â€š - settings (feature flag) ââ€â€š
ââ€â€Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€â‚¬Ã¢â€Ëœ
```
### Data Flow
1. **Plugin Activation**
- Admin enables plugin from Plugins page
- Settings table updated with `custom_fields_enabled = 1`
- Redis cache cleared
- Feature becomes available across the system
2. **Field Creation**
- Admin creates custom field via UI
- Field definition stored in `custom_fields` table
- Options stored in `custom_field_options` if applicable
- Cache invalidated
3. **Field Assignment**
- Admin assigns field to page identifier
- Assignment stored with role/plan filters
- Cache invalidated for affected pages
4. **Form Rendering**
- Frontend requests fields for page identifier
- System checks cache first (60s TTL)
- If cache miss, queries database with role/plan filtering
- Returns merged fields from sets and individual assignments
- Frontend renders fields dynamically
5. **Data Storage**
- Form submission includes custom field values
- Values stored in separate `custom_field_values` table
- Linked to entity (video, user, etc.) via entity_id and entity_type
## Database Schema
### custom_fields
```sql
CREATE TABLE `custom_fields` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`key` varchar(255) NOT NULL UNIQUE,
`type` enum('text','textarea','select','multiselect','checkbox','radio','number','date','file') NOT NULL,
`required` tinyint(1) DEFAULT 0,
`validation` json DEFAULT NULL,
`premium_only` tinyint(1) DEFAULT 0,
`active` tinyint(1) DEFAULT 1,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
);
```
### custom_field_options
```sql
CREATE TABLE `custom_field_options` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`custom_field_id` int(11) NOT NULL,
`label` varchar(255) NOT NULL,
`value` varchar(255) NOT NULL,
`order` int(11) DEFAULT 0,
`active` tinyint(1) DEFAULT 1,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`custom_field_id`) REFERENCES `custom_fields` (`id`) ON DELETE CASCADE
);
```
### custom_field_assignments
```sql
CREATE TABLE `custom_field_assignments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`custom_field_id` int(11) NOT NULL,
`page_identifier` varchar(255) NOT NULL,
`roles` json DEFAULT NULL,
`plans` json DEFAULT NULL,
`required_override` tinyint(1) DEFAULT NULL,
`premium_only_override` tinyint(1) DEFAULT NULL,
`order` int(11) DEFAULT 0,
`active` tinyint(1) DEFAULT 1,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`custom_field_id`) REFERENCES `custom_fields` (`id`) ON DELETE CASCADE
);
```
### custom_field_values (New table to be created)
```sql
CREATE TABLE `custom_field_values` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`custom_field_id` int(11) NOT NULL,
`entity_type` varchar(50) NOT NULL, -- 'video', 'user', 'subscription', etc.
`entity_id` int(11) NOT NULL,
`value` text,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`custom_field_id`) REFERENCES `custom_fields` (`id`) ON DELETE CASCADE,
INDEX `idx_entity` (`entity_type`, `entity_id`)
);
```
### settings (existing table)
```sql
ALTER TABLE `settings` ADD COLUMN `custom_fields_enabled` tinyint(1) DEFAULT 0;
```
## API Endpoints
### Feature Management
```
GET /admin/features/custom-fields # Get feature status
PATCH /admin/features/custom-fields # Toggle feature { enabled: true/false }
```
### Field Management
```
GET /admin/custom-fields # List all fields
POST /admin/custom-fields # Create field
GET /admin/custom-fields/:id # Get field details
PATCH /admin/custom-fields/:id # Update field
DELETE /admin/custom-fields/:id # Delete field
```
### Field Assignment
```
GET /admin/custom-fields-assign # List assignments
POST /admin/custom-fields-assign # Create assignment
DELETE /admin/custom-fields-assign/:id # Delete assignment
```
### Field Sets
```
GET /admin/custom-field-sets # List sets
POST /admin/custom-field-sets # Create set
GET /admin/custom-field-sets/:id # Get set details
PATCH /admin/custom-field-sets/:id # Update set
DELETE /admin/custom-field-sets/:id # Delete set
```
### Set Assignment
```
GET /admin/custom-field-set-assign # List set assignments
POST /admin/custom-field-set-assign # Create set assignment
DELETE /admin/custom-field-set-assign/:id # Delete set assignment
```
### Form Integration
```
GET /admin/custom-fields-for-form?pageIdentifier=videos.edit&roleId=1&planId=2
```
### Value Storage (To be implemented)
```
POST /admin/custom-field-values # Store field values
GET /admin/custom-field-values/:entityType/:entityId # Get values for entity
PATCH /admin/custom-field-values # Update field values
DELETE /admin/custom-field-values/:id # Delete field value
```
## Plugin Activation System
### Plugins Management Page Design
```jsx
// BACKEND/src/Pages/Plugins/PluginsManagement.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { adminApiHeader, finalRouteApi } from '../../components/CommonApis/CommonApiUrl';
import AdminSwitch from '../../components/CommonComponents/AdminSwitch';
const PluginsManagement = () => {
const [plugins, setPlugins] = useState([
{
id: 'custom_fields',
name: 'Custom Fields',
description: 'Add custom fields to any form in the admin panel',
icon: 'ðŸ“Â',
enabled: false,
configurable: true
}
// Future plugins can be added here
]);
useEffect(() => {
fetchPluginStatus();
}, []);
const fetchPluginStatus = async () => {
try {
const response = await axios.get(
`${finalRouteApi}/admin/features/custom-fields`,
{ headers: adminApiHeader }
);
setPlugins(prev => prev.map(plugin =>
plugin.id === 'custom_fields'
? { ...plugin, enabled: response.data.enabled }
: plugin
));
} catch (error) {
console.error('Error fetching plugin status:', error);
}
};
const togglePlugin = async (pluginId, enabled) => {
if (pluginId === 'custom_fields') {
try {
await axios.patch(
`${finalRouteApi}/admin/features/custom-fields`,
{ enabled },
{ headers: adminApiHeader }
);
setPlugins(prev => prev.map(plugin =>
plugin.id === pluginId
? { ...plugin, enabled }
: plugin
));
} catch (error) {
console.error('Error toggling plugin:', error);
}
}
};
return (
Plugins Management
{plugins.map(plugin => (
{plugin.icon}
{plugin.name}
{plugin.description}
togglePlugin(plugin.id, e.target.checked)}
/>
{plugin.configurable && plugin.enabled && (
)}
))}
);
};
```
### Route Configuration
Add to `BACKEND/src/AllRoutes.jsx`:
```jsx
// Import the component
import PluginsManagement from './Pages/Plugins/PluginsManagement';
// Add route
} />
```
### Menu Integration
Add to admin navigation menu:
```jsx
{
name: "Plugins",
icon: "ðŸâ€Å’",
path: "/admin/plugins",
roleRequired: 0 // Super admin only
}
```
## Frontend Integration
### Dynamic Field Renderer Component
```jsx
// BACKEND/src/components/CustomFields/DynamicFieldRenderer.jsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { adminApiHeader, finalRouteApi } from '../CommonApis/CommonApiUrl';
import AdminCustomInput from '../CommonComponents/AdminCustomInput';
import AdminCustomArea from '../CommonComponents/AdminCustomArea';
import AdminCustomSelect from '../CommonComponents/AdminCustomSelect';
import AdminCheckBox from '../CommonComponents/AdminCheckBox';
import AdminCustomRadio from '../CommonComponents/AdminCustomRadio';
const DynamicFieldRenderer = ({
pageIdentifier,
roleId,
planId,
values = {},
onChange,
errors = {}
}) => {
const [fields, setFields] = useState([]);
const [rules, setRules] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchFields();
}, [pageIdentifier, roleId, planId]);
const fetchFields = async () => {
try {
const response = await axios.get(
`${finalRouteApi}/custom-fields-for-form`,
{
params: { pageIdentifier, roleId, planId },
headers: adminApiHeader
}
);
setFields(response.data.fields || []);
setRules(response.data.rules || []);
setLoading(false);
} catch (error) {
console.error('Error fetching custom fields:', error);
setLoading(false);
}
};
const evaluateConditions = (field) => {
const fieldRules = rules.filter(r => r.target_field_id === field.id);
for (const rule of fieldRules) {
const sourceValue = values[`custom_${rule.source_field_id}`];
let conditionMet = false;
switch (rule.operator) {
case 'equals':
conditionMet = sourceValue == rule.value;
break;
case 'notEquals':
conditionMet = sourceValue != rule.value;
break;
case 'in':
conditionMet = JSON.parse(rule.value).includes(sourceValue);
break;
case 'notIn':
conditionMet = !JSON.parse(rule.value).includes(sourceValue);
break;
case 'greaterThan':
conditionMet = parseFloat(sourceValue) > parseFloat(rule.value);
break;
case 'lessThan':
conditionMet = parseFloat(sourceValue) < parseFloat(rule.value);
break;
}
if (rule.effect === 'hide' && conditionMet) return false;
if (rule.effect === 'show' && !conditionMet) return false;
if (rule.effect === 'require' && conditionMet) {
field.required = true;
}
}
return true;
};
const renderField = (field) => {
const fieldKey = `custom_${field.id}`;
const value = values[fieldKey] || '';
const error = errors[fieldKey];
if (!evaluateConditions(field)) return null;
switch (field.type) {
case 'text':
return (
onChange(fieldKey, e.target.value)}
error={error}
required={field.required}
placeholder={field.validation?.placeholder}
/>
);
case 'textarea':
return (
onChange(fieldKey, e.target.value)}
error={error}
required={field.required}
rows={field.validation?.rows || 4}
/>
);
case 'select':
return (
onChange(fieldKey, e.target.value)}
error={error}
required={field.required}
>
{field.options.map(opt => (
))}
);
case 'multiselect':
return (
{error && {error}}
);
case 'checkbox':
return (
onChange(fieldKey, e.target.checked ? '1' : '0')}
error={error}
/>
);
case 'radio':
return (
{field.options.map(opt => (
onChange(fieldKey, opt.value)}
/>
))}
{error && {error}}
);
case 'number':
return (
onChange(fieldKey, e.target.value)}
error={error}
required={field.required}
min={field.validation?.min}
max={field.validation?.max}
/>
);
case 'date':
return (
onChange(fieldKey, e.target.value)}
error={error}
required={field.required}
min={field.validation?.minDate}
max={field.validation?.maxDate}
/>
);
case 'file':
return (
onChange(fieldKey, e.target.files[0])}
accept={field.validation?.accept}
/>
{error && {error}}
);
default:
return null;
}
};
if (loading) return Loading custom fields...
;
if (!fields.length) return null;
return (
Additional Information
{fields.map(field => renderField(field))}
);
};
export default DynamicFieldRenderer;
```
### Integration in Existing Forms
Example integration in Video Edit form:
```jsx
// In VideoAddUpdate.jsx
import DynamicFieldRenderer from '../../../components/CustomFields/DynamicFieldRenderer';
// Add state for custom fields
const [customFieldValues, setCustomFieldValues] = useState({});
const [customFieldErrors, setCustomFieldErrors] = useState({});
// In the form render
{
setCustomFieldValues(prev => ({ ...prev, [key]: value }));
setCustomFieldErrors(prev => ({ ...prev, [key]: '' }));
}}
errors={customFieldErrors}
/>
// In form submission
const submitForm = async () => {
// Include custom field values
const payload = {
...formData,
custom_fields: customFieldValues
};
// Submit to API
await axios.post(updateApi, payload, { headers });
};
```
## Page Identifiers
Recommended page identifiers for different admin forms:
### Video Management
- `videos.create` - Create new video
- `videos.edit` - Edit video
- `series.create` - Create new series
- `series.edit` - Edit series
- `episodes.create` - Create new episode
- `episodes.edit` - Edit episode
### Audio Management
- `audio.create` - Create new audio
- `audio.edit` - Edit audio
- `albums.create` - Create new album
- `albums.edit` - Edit album
- `artists.create` - Create new artist
- `artists.edit` - Edit artist
### User Management
- `users.create` - Create new user
- `users.edit` - Edit user
- `users.import` - Import users
### Subscription Management
- `subscriptions.create` - Create subscription plan
- `subscriptions.edit` - Edit subscription plan
### Partner Management
- `partners.create` - Create partner account
- `partners.edit` - Edit partner account
- `partners.settings` - Partner settings
### Other Forms
- `categories.create` - Create category
- `categories.edit` - Edit category
- `playlists.create` - Create playlist
- `playlists.edit` - Edit playlist
- `live_streams.create` - Create live stream
- `live_streams.edit` - Edit live stream
## Implementation Guidelines
### 1. Backend Implementation Steps
#### Step 1: Create Migration for custom_field_values
```javascript
// migrations/20250825000000-create-custom-field-values.js
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('custom_field_values', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
custom_field_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'custom_fields',
key: 'id'
},
onDelete: 'CASCADE'
},
entity_type: {
type: Sequelize.STRING(50),
allowNull: false
},
entity_id: {
type: Sequelize.INTEGER,
allowNull: false
},
value: {
type: Sequelize.TEXT
},
created_at: {
type: Sequelize.DATE,
allowNull: false
},
updated_at: {
type: Sequelize.DATE,
allowNull: false
}
});
await queryInterface.addIndex('custom_field_values',
['entity_type', 'entity_id'],
{ name: 'idx_entity' }
);
},
down: async (queryInterface) => {
await queryInterface.dropTable('custom_field_values');
}
};
```
#### Step 2: Create Model
```javascript
// models/custom_field_value.js
module.exports = (sequelize, DataTypes) => {
const CustomFieldValue = sequelize.define('CustomFieldValue', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
custom_field_id: {
type: DataTypes.INTEGER,
allowNull: false
},
entity_type: {
type: DataTypes.STRING(50),
allowNull: false
},
entity_id: {
type: DataTypes.INTEGER,
allowNull: false
},
value: {
type: DataTypes.TEXT
}
}, {
tableName: 'custom_field_values',
underscored: true,
timestamps: true
});
CustomFieldValue.associate = function(models) {
CustomFieldValue.belongsTo(models.CustomField, {
foreignKey: 'custom_field_id',
as: 'field'
});
};
return CustomFieldValue;
};
```
#### Step 3: Add Value Management to Controller
```javascript
// Add to AdminCustomFieldsController.js
exports.storeValues = async (req, res) => {
const transaction = await db.sequelize.transaction();
try {
const { entity_type, entity_id, values } = req.body;
// Delete existing values
await db.CustomFieldValue.destroy({
where: { entity_type, entity_id },
transaction
});
// Insert new values
const records = [];
for (const [key, value] of Object.entries(values)) {
if (key.startsWith('custom_')) {
const field_id = parseInt(key.replace('custom_', ''));
records.push({
custom_field_id: field_id,
entity_type,
entity_id,
value: typeof value === 'object' ? JSON.stringify(value) : value
});
}
}
if (records.length) {
await db.CustomFieldValue.bulkCreate(records, { transaction });
}
await transaction.commit();
res.status(200).json({ status: true, message: 'Values saved' });
} catch (error) {
await transaction.rollback();
res.status(500).json({ status: false, message: error.message });
}
};
exports.getValues = async (req, res) => {
try {
const { entity_type, entity_id } = req.params;
const values = await db.CustomFieldValue.findAll({
where: { entity_type, entity_id },
include: [{ model: db.CustomField, as: 'field' }]
});
const formatted = {};
values.forEach(v => {
formatted[`custom_${v.custom_field_id}`] = v.value;
});
res.status(200).json({ status: true, values: formatted });
} catch (error) {
res.status(500).json({ status: false, message: error.message });
}
};
```
### 2. Frontend Implementation Steps
#### Step 1: Create Custom Fields Management Page
```jsx
// BACKEND/src/Pages/CustomFields/CustomFieldsManagement.jsx
import React from 'react';
import { Tab, Tabs } from 'react-bootstrap';
import ManageFields from './ManageFields';
import AssignFields from './AssignFields';
import FieldSets from './FieldSets';
const CustomFieldsManagement = () => {
return (
);
};
export default CustomFieldsManagement;
```
#### Step 2: Integrate with Existing Forms
```javascript
// Helper function to load custom field values
const loadCustomFieldValues = async (entityType, entityId) => {
try {
const response = await axios.get(
`${finalRouteApi}/custom-field-values/${entityType}/${entityId}`,
{ headers: adminApiHeader }
);
return response.data.values || {};
} catch (error) {
console.error('Error loading custom field values:', error);
return {};
}
};
// Helper function to save custom field values
const saveCustomFieldValues = async (entityType, entityId, values) => {
try {
await axios.post(
`${finalRouteApi}/custom-field-values`,
{
entity_type: entityType,
entity_id: entityId,
values
},
{ headers: adminApiHeader }
);
} catch (error) {
console.error('Error saving custom field values:', error);
throw error;
}
};
```
## Security & Performance
### Security Considerations
1. **Authentication & Authorization**
- All endpoints require admin JWT token
- Feature toggle checks before access
- Role-based field visibility
2. **Input Validation**
- Server-side validation for all inputs
- XSS prevention through proper encoding
- SQL injection prevention via Sequelize ORM
3. **File Upload Security**
- File type validation
- File size limits
- Virus scanning integration recommended
### Performance Optimization
1. **Caching Strategy**
- Redis cache with 60-second TTL
- Cache key pattern: `custom_fields:{pageIdentifier}:{roleId}:{planId}`
- Automatic cache invalidation on changes
2. **Database Optimization**
- Indexed columns for fast lookups
- Eager loading to reduce queries
- Bulk operations for multiple records
3. **Frontend Optimization**
- Lazy loading of field components
- Memoization of field rendering
- Debounced validation
## Usage Examples
### Example 1: Adding Custom Fields to Video Form
1. Enable Custom Fields plugin from Plugins page
2. Create fields:
```json
{
"name": "Director",
"key": "director",
"type": "text",
"required": true
}
```
3. Assign to `videos.edit` page
4. Fields automatically appear in video edit form
### Example 2: Creating a Field Set for Partner Information
1. Create field set "Partner Details"
2. Add fields:
- Company Name (text)
- Business Type (select)
- Tax ID (text)
3. Assign set to `partners.create` and `partners.edit`
### Example 3: Conditional Fields
1. Create "Content Type" select field with options: Movie, Series
2. Create "Season" number field
3. Add conditional rule:
- If Content Type equals "Series", show Season field
- Otherwise, hide Season field
## Troubleshooting
### Common Issues
1. **Fields not appearing**
- Check if plugin is enabled
- Verify field assignment to correct page identifier
- Check role/plan restrictions
2. **Cache not updating**
- Ensure Redis is running
- Check Redis connection settings
- Manual cache clear may be needed
3. **Validation errors**
- Review field validation rules
- Check required field settings
- Verify data types match
### Debug Mode
Enable debug logging:
```javascript
// In AdminCustomFieldsController.js
const DEBUG = process.env.CUSTOM_FIELDS_DEBUG === 'true';
if (DEBUG) {
console.log('Custom Fields Debug:', {
pageIdentifier,
roleId,
planId,
fieldsFound: fields.length
});
}
```
## Quick Setup Guide
**IMPORTANT: If you're seeing "No database selected" error, the plugin is likely disabled.**
Follow these steps to get Custom Fields working quickly:
### 1. Enable the Plugin
```bash
# Option 1: Use the helper script (RECOMMENDED)
node NODE_MICROSERVICES/scripts/enable_custom_fields.js
# Option 2: Manual database update
node -e "const Setting = require('./NODE_MICROSERVICES/models/setting'); Setting.findOne().then(s => s.update({custom_fields_enabled: 1}));"
```
### 2. Verify Installation
```bash
# Run the validation script to check everything is working
node NODE_MICROSERVICES/scripts/validate_custom_fields_setup.js
```
Expected output for a working setup:
```
🔠Custom Fields Plugin Setup Validation
1. Testing database connection...
✓ Database connection successful
2. Checking feature flag...
✓ Custom Fields feature is enabled
3. Checking database tables...
✓ All required tables exist
4. Checking Sequelize models...
✓ All models loaded successfully
5. Testing basic operations...
✓ Dashboard endpoint works (enabled: true)
📋 Summary:
✅ Custom Fields plugin is properly set up and ready to use!
```
### 3. Test Basic Functionality
1. **Create a Test Field via API:**
```bash
curl -X POST http://localhost:3000/admin/custom-fields \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ADMIN_JWT" \
-d '{
"name": "Test Field",
"key": "test_field",
"type": "text",
"required": false,
"active": true
}'
```
2. **Assign Field to a Page:**
```bash
curl -X POST http://localhost:3000/admin/custom-fields-assign \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ADMIN_JWT" \
-d '{
"custom_field_id": 1,
"page_identifier": "videos.edit",
"active": true
}'
```
3. **Retrieve Fields for Form:**
```bash
curl "http://localhost:3000/admin/custom-fields-for-form?pageIdentifier=videos.edit" \
-H "Authorization: Bearer YOUR_ADMIN_JWT"
```
### 4. Integration with Frontend
Add the Dynamic Field Renderer to your forms:
```jsx
import DynamicFieldRenderer from '../components/CustomFields/DynamicFieldRenderer';
// In your form component
setCustomFieldValues(prev => ({ ...prev, [key]: value }))}
errors={customFieldErrors}
/>
```
## Enhanced Troubleshooting
### Most Common Issues and Solutions
#### 🔴 "No database selected" Error
**Root Cause:** Custom Fields feature is disabled in the database.
**Quick Fix:**
```bash
# Check current status
node -e "const Setting = require('./NODE_MICROSERVICES/models/setting'); Setting.findOne().then(s => console.log('custom_fields_enabled:', s?.custom_fields_enabled || 'SETTINGS_NOT_FOUND'));"
# Enable the feature
node NODE_MICROSERVICES/scripts/enable_custom_fields.js
# Verify it worked
node NODE_MICROSERVICES/scripts/validate_custom_fields_setup.js
```
#### 🔴 API Returns 404 for Custom Fields Endpoints
**Causes and Solutions:**
1. **Feature Disabled:** Run `node scripts/enable_custom_fields.js`
2. **Missing Models:** Check `NODE_MICROSERVICES/models/` directory
3. **Migration Issues:** Run `npx sequelize-cli db:migrate`
4. **Invalid JWT:** Verify admin authentication token
#### 🔴 Tables Don't Exist Errors
```bash
# Check migration status
npx sequelize-cli db:migrate:status
# Run pending migrations
npx sequelize-cli db:migrate
# Verify tables exist
node -e "const db = require('./NODE_MICROSERVICES/models'); db.sequelize.getQueryInterface().showAllTables().then(tables => console.log('Tables:', tables.filter(t => t.includes('custom_field'))));"
```
#### 🔴 Model Loading Issues
**Check models exist:**
```bash
ls NODE_MICROSERVICES/models/ | grep custom
```
**Expected files:**
- `custom_field.js`
- `custom_field_option.js`
- `custom_field_assignment.js`
- `custom_field_value.js`
- `custom_field_set.js`
- `custom_field_set_item.js`
- `custom_field_set_assignment.js`
- `conditional_rule.js`
### Database Validation Queries
```sql
-- Check feature status
SELECT custom_fields_enabled FROM settings LIMIT 1;
-- List all custom fields
SELECT id, field_label as name, field_key as key, field_type as type, is_active
FROM custom_fields;
-- Check field assignments
SELECT cfa.*, cf.field_label as field_name
FROM custom_field_assignments cfa
JOIN custom_fields cf ON cfa.custom_field_id = cf.id
WHERE page_identifier = 'videos.edit';
-- Verify table structures
DESCRIBE custom_fields;
DESCRIBE custom_field_values;
```
### Error Message Reference
| Error Message | Root Cause | Solution Command |
|---------------|------------|------------------|
| `No database selected` | Feature disabled | `node scripts/enable_custom_fields.js` |
| `custom_fields table doesn't exist` | Missing migrations | `npx sequelize-cli db:migrate` |
| `Cannot find module 'CustomField'` | Missing model files | Check `models/` directory |
| `field_id column doesn't exist` | Legacy schema mismatch | `npm run cf:normalize` |
| `ECONNREFUSED 127.0.0.1:6379` | Redis not available | Non-critical, caching disabled |
| `Access denied` | Wrong JWT token | Check admin authentication |
### Manual Recovery Steps
If automated scripts fail:
1. **Database Connection:**
```bash
node -e "require('./NODE_MICROSERVICES/models').sequelize.authenticate().then(() => console.log('DB OK')).catch(e => console.error('DB Error:', e.message));"
```
2. **Enable Feature Manually:**
```sql
UPDATE settings SET custom_fields_enabled = 1;
```
3. **Create Missing Tables:**
```bash
npx sequelize-cli db:migrate --to 20250901180500-rename-custom-fields-columns-to-legacy.js
```
4. **Reset Cache:**
```bash
node -e "const {redis} = require('./NODE_MICROSERVICES/config/libraryqueue'); redis.del('custom_fields:*').then(() => console.log('Cache cleared')).catch(() => console.log('Redis not available'));"
```
## Future Enhancements
1. **Advanced Field Types**
- Rich text editor
- Image upload with preview
- Location picker
- Color picker
2. **Import/Export**
- Export field configurations
- Import from JSON/CSV
- Field templates
3. **Advanced Logic**
- Complex conditional rules
- Field calculations
- Dynamic options loading
4. **Integration**
- API for external systems
- Webhook notifications
- Third-party field types
## Conclusion
The Custom Fields Plugin System provides a flexible and powerful way to extend forms throughout the admin panel.
**Key Points:**
- Always enable the feature first using `node scripts/enable_custom_fields.js`
- Use the validation script to diagnose issues
- The "No database selected" error almost always means the feature is disabled
- All database tables and models should exist after running migrations
- Redis connection issues are non-blocking and won't prevent functionality
By following this documentation and using the provided scripts, you can successfully implement and maintain custom fields across the platform.
RULE
uHOQZBKH7fNW79cobhC9VD