For the complete documentation index, see llms.txt. This page is also available as Markdown.

Component Cookbook

Copy-paste recipes for custom components, from static layouts to live-data and journey-aware blocks. Each one is real code you can paste into the Code view of the component editor (Content → Component Library) and adapt. For the underlying model — the {{field}} format, data bindings, and the sandbox rules — see Building Custom Components.

Two ways to use data

Components reach data two ways, and the recipes below use both:

  • Declarative bindings — {{data.<name>.path}}. Resolved on the server at render time and substituted into your HTML. Best for scalar values you want to print (a name, a balance, a status). Identity is supplied by the platform, scope-checked and audited.

  • The runtime bridge — window.PC.call(channel, action, params). A narrow postMessage RPC injected into every component. Best for interactivity — reading or advancing the journey, resolving live pricing, fetching the catalog. The sandbox sets connect-src 'none', so window.PC.call is the only way a component reaches out; direct fetch is blocked.


Recipe 1 — A field-driven CTA hero

A pure-layout block (no data) whose text and link are author-editable fields. The simplest kind of component, and the most common.

<style>
  .hero { padding: 56px 32px; border-radius: 16px; background: linear-gradient(135deg,#5B4BD6,#4A3CC0); color: #fff; text-align: center; font-family: system-ui, -apple-system, sans-serif; }
  .hero h1 { font-size: 2rem; margin: 0 0 12px 0; }
  .hero p { font-size: 1.0625rem; opacity: 0.9; margin: 0 0 24px 0; }
  .hero a { display: inline-block; padding: 12px 28px; background: #fff; color: #4A3CC0; border-radius: 8px; font-weight: 600; text-decoration: none; }
</style>
<div class="hero">
  <h1>{{headline}}</h1>
  <p>{{subhead}}</p>
  <a href="{{ctaHref}}">{{ctaText}}</a>
</div>

Fields: headline (text), subhead (text), ctaText (text), ctaHref (text).


Recipe 2 — An FAQ accordion (no JavaScript)

Native <details>/<summary> gives you collapsible sections without any script — which keeps the component clean under the sandbox. Fields fill the questions and answers.

Fields: q1q3 and a1a3 (use textarea for the answers).


Recipe 3 — A personalized account summary card

Prints live identity and billing data using declarative bindings. Declare two bindings on the component:

  • viewer — kind viewer, no params.

  • account — kind currentAccount, params { "accountId": {"$ctx":"account.id"} } (the $ctx token binds it to the signed-in viewer's own account, filled server-side).

Bindings: viewer{ isAuthenticated, id, email, role, displayName, tenantName, … }; account (currentAccount) → { accountNumber, name, status, balance, currency, mrr, nextBillingDate, … }.


Recipe 4 — A "most recent invoice" card

Declarative bindings can index into a list with a numbered path, so you can surface the first item without any script. Declare an invoices binding (kind invoices, params { "accountId": {"$ctx":"account.id"}, "filter": "open", "limit": 1 }) and read {{data.invoices.0.…}} — the first invoice returned.

Binding: invoicesArray<{ id, invoiceNumber, invoiceDate, dueDate, amount, balance, currency, status, paidAt }>. (Indexed paths print one item; for a full, variable-length table, use the editor's Data panel rather than the template.)


Recipe 5 — A custom "Continue" button inside a journey

When a component runs as a journey step (and the journey hides its default chrome), the component can drive the flow itself through the journey-actions channel. Attach the handler with addEventListener in an inline <script>inline onclick="…" attributes are blocked by the sandbox, but inline <script> blocks are allowed.

You can also read the journey's context the same way — await window.PC.call('journey-actions', 'getVariables') resolves to { ok: true, result: { …variables } } — to prefill the component from earlier steps.


window.PC quick reference

window.PC.call(channel, action, params) returns a Promise<{ ok, result?, error? }>.

journey-actions (only when the component runs inside a journey step; otherwise every action returns { ok:false, error:"no-journey-host" })

action
does

getVariables

read the journey's shared context

setVariables

write string values into the context

advanceStep

move to the next step (optionally passing values)

goBack

return to the previous step

markStepCompleted / resetStepCompleted

toggle the step's completion

data (always available; the parent proxies to public endpoints since the iframe can't fetch)

action
does

product-catalog

the sellable catalog

pricing-resolve

resolve live pricing for a selection

product-set-resolved

resolve a product set

get-context

the current page/journey context

navigate

navigate the parent (e.g. to a thank-you page)

hosted-pages

open the billing provider's hosted payment page

Unknown channels or actions return { ok:false, error:"unknown-action" }, and every input is validated — only the named fields are forwarded.

Tips

  • Prefer {{data}} for printing, window.PC.call for doing. Scalars and single items render cleanest as declarative bindings; interactivity and journey control go through the bridge.

  • No inline event handlers. Attach listeners with addEventListener in an inline <script>; onclick="…" attributes are flagged by the security scanner.

  • No outbound fetch. connect-src 'none' blocks it by design — reach data through a declared binding or a data-channel action instead.

  • Bind to the viewer's own account. Use {"$ctx":"account.id"} for accountId so a customer can only ever read their own data.

Last updated

Was this helpful?