mirror of
https://github.com/xtr-dev/payload-feature-flags.git
synced 2025-12-10 02:43:25 +00:00
Add Payload Feature Flags plugin with custom endpoints and configurations
This commit is contained in:
234
src/index.ts
Normal file
234
src/index.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import type { Config, CollectionConfig, Field } from 'payload'
|
||||
|
||||
import { customEndpointHandler } from './endpoints/customEndpointHandler.js'
|
||||
|
||||
export type CollectionOverrides = Partial<
|
||||
Omit<CollectionConfig, 'fields'>
|
||||
> & {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user