# 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 (

Custom Fields Management

); }; 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