Personalization

Saying something different to a campaign, an audience, or one arm of an experiment.

Audiences

An audience is a group of visitors this page can say something different to. You declare it once for the page, in a block's Rules tab, and every block that changes for it points at the same definition, so "the spring campaign" means one thing across the page.

An audience can read three kinds of fact:

SourceAttributeComes from
URL paramparam.utm_campaignthe visitor's own query string
Experimentexperiment.homepage_herothe variant they were assigned
Attributeplan, regionwhatever your app passes as userAttributes

The param. and experiment. prefixes are reserved, so a rule can never be confused by an attribute your site happens to name the same thing.

Editing for an audience

Pick the audience in the top bar and edit normally. Every control in the Content and Style tabs now writes to that audience: change the headline, swap a dropdown, set a different background, and each field you touch gets a dot and a reset beside it. Everything you leave alone keeps following the copy everyone else sees, so there is no second version of the section to maintain.

This is the same gesture as editing mobile styles: switch the canvas to a phone and the Style tab edits the mobile bucket. Switch to an audience and the whole panel edits that audience.

Switch back to Everyone to edit what everyone sees.

Experiments are audiences

Starting an experiment creates one audience per variant past the control, named after the arm. They appear in the same switch under Experiment arms, and picking one also sets the canvas to that bucket. Authoring what an arm says is therefore the same as authoring what a campaign says, over every field rather than only the text ones.

If a visitor is in an arm and a campaign at once, both apply, in the page's audience order, and a later one wins a field an earlier one also sets.

Where a rule is decided

Audiences defined by your own attributes or by an experiment arm are resolved at delivery, server-side, before the HTML is cached — and their overrides are then stripped from the response, so no visitor receives the copy written for another audience.

An audience keyed on the URL cannot be resolved that way: a page built ahead of time has no visitor query string, and deciding it early would cache one campaign's answer for everyone. Those audiences travel with the content and the SDK applies them in a layout effect, before the first paint. That is what makes campaign links work on a statically generated or ISR page with no extra wiring.

If your page is rendered per request, hand the query string over and they are resolved server-side too, with nothing left for the browser to do:

tsx
// App Router, rendered per request
export default async function Page({ searchParams }) {
  const query = await searchParams
  const entry = await fetchOneEntry({
    model: 'page',
    publicKey: KEY,
    urlPath: '/landings/formula',
    searchParams: query,
    userAttributes: { plan: user.plan },
  })
  if (isContentBindError(entry)) return null
  return <Content model="page" content={entry} publicKey={KEY} searchParams={query} />
}

Previewing

Picking an audience already puts the canvas in that visitor's shoes. Preview as a visitor is for everything else: type an attribute, or a URL parameter like utm_campaign / formula, to see the page exactly as delivery would build it.

With nothing set, the canvas shows the copy everyone gets, and blocks hidden by a rule stay visible but dimmed and labelled — authoring a page should not mean hunting for blocks that vanished.

Passing your own attributes

tsx
const entry = await fetchOneEntry({
  model: 'page',
  publicKey: KEY,
  urlPath,
  userAttributes: { plan: user.plan, region: geo.country },
})
if (isContentBindError(entry)) return null

Declare them once in workspace settings, with example values, and they become pickable in the rule builder rather than free-form strings authors have to spell correctly. Every distinct combination is a distinct cache key; the editor tracks the total and warns before it multiplies past the point where the cache stops helping.

All documentation · llms.txt · llms-full.txt · contentbind.com