import type { Config, CollectionConfig, Field } from 'payload' import { customEndpointHandler } from './endpoints/customEndpointHandler.js' export type CollectionOverrides = Partial< Omit > & { fields?: (args: { defaultFields: Field[] }) => Field[] } export type PayloadFeatureFlagsConfig = { /** * Enable/disable the plugin * @default false */ disabled?: boolean /** * Default value for new feature flags * @default false */ defaultValue?: boolean /** * Enable percentage-based rollouts * @default true */ enableRollouts?: boolean /** * Enable variant/experiment support (A/B testing) * @default true */ enableVariants?: boolean /** * Enable REST API endpoints for feature flags * @default false */ enableApi?: boolean /** * Override collection configuration */ collectionOverrides?: CollectionOverrides } export const payloadFeatureFlags = (pluginOptions: PayloadFeatureFlagsConfig = {}) => (config: Config): Config => { const { disabled = false, defaultValue = false, enableRollouts = true, enableVariants = true, enableApi = false, collectionOverrides = {}, } = pluginOptions // Get collection slug from overrides or use default const collectionSlug = collectionOverrides.slug || 'feature-flags' if (!config.collections) { config.collections = [] } // Define default fields const defaultFields: Field[] = [ { name: 'name', type: 'text', required: true, unique: true, admin: { description: 'Unique identifier for the feature flag', }, }, { name: 'description', type: 'textarea', admin: { description: 'Describe what this feature flag controls', }, }, { name: 'enabled', type: 'checkbox', defaultValue: defaultValue, required: true, admin: { description: 'Toggle this feature flag on or off', }, }, ...(enableRollouts ? [ { name: 'rolloutPercentage', type: 'number', min: 0, max: 100, defaultValue: 100, admin: { description: 'Percentage of users who will see this feature (0-100)', condition: (data: any) => data?.enabled === true, }, }, ] : []), ...(enableVariants ? [ { name: 'variants', type: 'array', admin: { description: 'Define variants for A/B testing', condition: (data: any) => data?.enabled === true, }, fields: [ { name: 'name', type: 'text', required: true, admin: { description: 'Variant identifier (e.g., control, variant-a)', }, }, { name: 'weight', type: 'number', min: 0, max: 100, required: true, admin: { description: 'Weight for this variant (all weights should sum to 100)', }, }, { name: 'metadata', type: 'json', admin: { description: 'Additional data for this variant', }, }, ], }, ] : []), { name: 'tags', type: 'array', fields: [ { name: 'tag', type: 'text', }, ], admin: { description: 'Tags for organizing feature flags', }, }, { name: 'metadata', type: 'json', admin: { description: 'Additional metadata for this feature flag', }, }, ] // Apply field overrides if provided const fields = collectionOverrides.fields ? collectionOverrides.fields({ defaultFields }) : defaultFields // Extract field overrides from collectionOverrides const { fields: _fieldsOverride, ...otherOverrides } = collectionOverrides // Create the feature flags collection with overrides const featureFlagsCollection: CollectionConfig = { slug: collectionSlug, admin: { useAsTitle: 'name', group: 'Configuration', description: 'Manage feature flags for your application', ...(otherOverrides.admin || {}), }, fields, // Apply any other collection overrides ...otherOverrides, } config.collections.push(featureFlagsCollection) /** * If the plugin is disabled, we still want to keep the collection * so the database schema is consistent which is important for migrations. */ if (disabled) { return config } if (!config.endpoints) { config.endpoints = [] } if (!config.admin) { config.admin = {} } if (!config.admin.components) { config.admin.components = {} } if (!config.admin.components.beforeDashboard) { config.admin.components.beforeDashboard = [] } config.admin.components.beforeDashboard.push( `payload-feature-flags/client#BeforeDashboardClient`, ) config.admin.components.beforeDashboard.push( `payload-feature-flags/rsc#BeforeDashboardServer`, ) // Add API endpoints if enabled if (enableApi) { // Add API endpoint for fetching feature flags config.endpoints.push({ handler: customEndpointHandler(collectionSlug), method: 'get', path: '/feature-flags', }) // Add endpoint for checking a specific feature flag config.endpoints.push({ handler: customEndpointHandler(collectionSlug), method: 'get', path: '/feature-flags/:flag', }) } return config }