Skip to Content
Delmare Digital

Flag points

Register a testable setting once, read it on the server or client, record exposure, and tag host content with a group.

A flag point is a stable place in your code that reads settings. You register it once; editors can then run any number of A setting tests on it from the admin without code changes.

Register a flag point

payload.config.ts
createExperimentsPlugin({
  // ...
  flagPoints: [{
    id: 'signup-prompt',
    label: 'Signup prompt',
    description: 'The prompt shown under the course list.',
    params: {
      heading: { type: 'text', label: 'Heading', required: true },
      style: {
        type: 'select',
        label: 'Style',
        options: [{ label: 'Banner', value: 'banner' }, { label: 'Card', value: 'card' }],
      },
    },
    defaultParams: { heading: 'Start learning', style: 'banner' },
  }],
})

defineFlagPoint from the package root is an optional typed identity helper for defining points in their own module.

Prop

Type

A ParamField is { type, label, required?, options?, min?, max? }, where type is text, textarea, number, checkbox or select, and select options are { label, value }.

Read it on the server

src/components/SignupPrompt.tsx
import { getFlagPoint } from '@delmaredigital/payload-experiments/rsc'
import { serverRuntime } from '@/experiments/serverRuntime'

export async function SignupPrompt() {
  const point = await getFlagPoint(serverRuntime, 'signup-prompt')
  const { heading } = point.params as { heading: string }
  await point.expose(point.arm)
  return <h2>{heading}</h2>
}

getFlagPoint(serverRuntime, id, { user? }) returns:

FieldMeaning
armThe group key this person sees
paramsThe settings to render
sourceexperiment when a test is assigning, otherwise default
experiment, experimentIdThe running test, when source is experiment
expose(renderedArm)Records a manual exposure

The settings come from the live test, then the saved winner defaults, then the point's defaultParams. Reading the point records nothing. Call await point.expose(point.arm) only when you actually deliver that version's content.

Read it on the client

Allow-list the point in toClientAssignments and use useFlagPoint inside an ExperimentsProvider:

src/components/SignupPromptClient.tsx
'use client'
import { useEffect } from 'react'
import { useFlagPoint } from '@delmaredigital/payload-experiments/client'

export function SignupPromptClient() {
  const { arm, params, expose } = useFlagPoint<{ heading: string }>('signup-prompt')
  // Record once this version is on screen. expose is a new function each render, so key on arm.
  useEffect(() => { expose(arm) }, [arm]) // eslint-disable-line react-hooks/exhaustive-deps
  return <h2>{params.heading}</h2>
}

An unlisted point throws in development and returns the Original with one console error in production. See Client counting.

Tag host content with a group

When versions need different content in your own collections (for example, a different form per version), add an arm-select field to that collection:

src/collections/SignupForms.ts
import { createArmSelectField } from '@delmaredigital/payload-experiments'

fields: [
  // ...
  createArmSelectField({ flagPoint: 'signup-form' }),
]

The field is named experimentArm by default (pass name, label or required to change it) and stores { experiment: String(experimentId), arm }. Match content on String(experimentId) plus the arm key, never on the test's short name. The field lists group labels, leaves out the holdout, warns about tags from an earlier test, and warns before changing content during a running test.

For collections tagged this way, record content changes so the results chart can show them: call recordContentChange({ doc, previousDoc, req, collection }) from /rsc in the collection's afterChange, and for collections with drafts add captureContentBaseline from /rsc to beforeChange. Neither ever throws into your save.

Code tests

A code test is like a flag test without a registered point: a developer reads it by short name and implements each version in code. On the client, allow-list it in toClientAssignments({ experiments: [...] }) and use useExperiment(slug), which returns { arm, params, expose }. Creating code tests needs access.admin.

Edit this page on GitHub

On this page