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 setsconnect-src 'none', sowindow.PC.callis the only way a component reaches out; directfetchis 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: q1–q3 and a1–a3 (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$ctxtoken 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: invoices → Array<{ 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" })
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)
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.callfor 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
addEventListenerin 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 adata-channel action instead.Bind to the viewer's own account. Use
{"$ctx":"account.id"}foraccountIdso a customer can only ever read their own data.
Last updated
Was this helpful?
