Skip to Content
Delmare Digital

Goals and conversions

Register event sources and metric source adapters so editors can pick your app's real outcomes as goals.

Editors choose goals from what your app makes measurable. There are two ways to expose an outcome: an event source, a named yes/no event you record when it happens, and a metric source, an adapter that reads outcomes from data you already have.

Event sources

For simple yes/no outcomes such as a signup, list event names in eventSources:

payload.config.ts
createExperimentsPlugin({
  // ...
  eventSources: [
    'newsletter_signup',
    { name: 'donation_completed', clientWritable: false },
  ],
})

Each name becomes a goal source, events:<name>, that editors see in the goal picker. Names must match /^[a-z0-9_]+$/ and be unique. A bare string allows browser writes; the object form sets clientWritable explicitly. Event sources support visitor and user tests and are always yes/no; they do not carry counts or revenue.

Record the event on the server with trackConversion:

src/app/api/donate/route.ts
import { trackConversion } from '@delmaredigital/payload-experiments/rsc'
import { serverRuntime } from '@/experiments/serverRuntime'

await trackConversion(serverRuntime, {
  eventName: 'donation_completed',
  idempotencyKey: `donation:${donation.id}`,
})

Prop

Type

For a client-writable source, the browser can call trackConversionBeacon(eventName, { idempotencyKey }) from /client inside a mounted provider. Client values are stored as 1. Beacons for a name that is not client-writable are dropped silently.

Metric sources

For counts, revenue, or outcomes already stored elsewhere (orders, enrolments, gifts), supply a metric source adapter:

src/experiments/metrics.ts
import { defineMetricSource } from '@delmaredigital/payload-experiments'

export const orders = defineMetricSource({
  id: 'orders',
  label: 'Order revenue',
  kind: 'revenue',
  keyedBy: 'user',
  query: async ({ from, to, unitIds }) => {
    const rows = await db.orders.findMany({ from, to, userIds: unitIds })
    return rows.map((order) => ({ unitId: order.userId, value: order.total, ts: order.createdAt }))
  },
})
payload.config.ts
createExperimentsPlugin({
  // ...
  metricSources: [orders],
})

Prop

Type

Rules for query:

  • Return one row per event, with ts as a Date. The plugin applies the attribution window and per-person aggregation itself.
  • A user test needs a user-keyed source. A visitor test can use a user-keyed source through linked identities.
  • The results runner bounds how long it waits for your promise, but cannot cancel it. Give your own database or API clients timeouts too.
  • A failed or timed-out source marks that goal unavailable for the run; the next run tries again.

For revenue goals, the same query is called over the 90 days before Start to resolve a percentile cap. covariate is described in CUPED and capping.

Revenue on the server only

Record revenue through trusted server code or a metric source, never a browser beacon. Browser conversions are only accepted for client-writable yes/no sources, and their values are stored as 1.

Edit this page on GitHub

On this page