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

Building Custom Components

When the built-in blocks don't cover a layout you need, you build your own custom component — a reusable block that becomes a first-class element in the page editor and the portals. You author components in Content → Component Library (and you can have Mango generate one for you — see Meet Mango in the help docs). This page is the developer reference: the code format, how fields work, how to bind live commerce data, and what the sandbox does and doesn't allow.

For the click-by-click editor walkthrough, see Creating a Component in the product docs. This page focuses on the code.

The component model

A code-type component is plain HTML + <style> with {{field}} placeholders. There's no build step and no framework — what you write is what renders. At render time PeakCommerce:

  • substitutes each {{field}} with the field's value,

  • substitutes each {{data.<name>}} with the result of a declared data binding,

  • HTML-escapes every substituted value (so a value containing <script> can never break out of the markup), and

  • renders the result inside a sandboxed iframe.

Two rules follow from that pipeline:

  • Templating is pure substitution — there is no expression evaluation inside {{ }}. A placeholder is a key or a dotted path, not JavaScript.

  • Unknown placeholders are left untouched. If you write {{titel}} and never declared a titel field, the literal {{titel}} stays in the output — so you can spot the typo instead of silently rendering nothing (or, worse, leaking unrelated data).

Fields — the editable props

Fields are the properties a page author can edit when they place your component. Each field has a name, a type, a label, a defaultValue, and (for select) comma-separated options:

Field type
Use it for

text

a single-line string

textarea

a multi-line string

number

a numeric value

select

a fixed set of choices (options: "info,success,warning")

radio

a single choice from a small set

Reference a field in your code as {{fieldName}}. The field's name is the token; the label is only what the author sees in the editor.

Example: a pricing card

A self-contained component with six text fields:

Its fields:

name
type
default

tierName

text

Professional

price

text

$49

feature1 / feature2 / feature3

text

Unlimited projects / Priority support / Advanced analytics

ctaText

text

Get Started

Example: an alert banner (a select driving a CSS class)

A field value can drive layout, not just text — here {{alertType}} is interpolated into a class name so the same component renders three styles:

Field: alertTypeselect, options info,success,warning, default info (plus icon, title, and message text fields).

Binding to live commerce data

Fields are author-set and static. To render live data — the signed-in customer's name, their account balance, their invoices — declare a data binding. A binding has a name (yours), a kind (a platform-provided source), and optional params. You then reference it in code as {{data.<name>}}, or drill into the payload with a dotted path {{data.<name>.field}}.

Bindings are resolved server-side, so a component never holds tokens or permissions, the viewer's identity is supplied by the platform (not trusted from the page), and every resolution is scope-checked, rate-limited, and audited.

Built-in binding kinds

kind
returns
auth

viewer

the current visitor's identity

public (works for anonymous too)

currentAccount

the billing account detail

signed-in + scope check

invoices

an account's invoices

signed-in + scope check

payments

an account's payments

signed-in + scope check

subscriptions

an account's subscriptions

signed-in + scope check

usage

usage drawdown for a subscription

signed-in + scope check

productCatalog

sellable products/plans

per configuration

The account-scoped kinds take an accountId param. Bind it to the current viewer's own account by passing the context token {"$ctx":"account.id"} rather than a hard-coded id — the resolver fills it from the authenticated viewer, so a customer can only ever read their own data.

Binding result shapes

  • viewer{ isAuthenticated, id, email, role, portalType, displayName, tenantId, tenantName } (identity only — never permissions or tokens).

  • currentAccount{ id, accountNumber, name, status, balance, currency, locale, mrr, createdDate, nextBillingDate, billToContact, soldToContact }.

Example: a personalized account banner

Declare two bindings — viewer (kind viewer, no params) and account (kind currentAccount, params { "accountId": {"$ctx":"account.id"} }) — then read them in code:

Because {{data.…}} substitution resolves a single value, it's ideal for scalar fields like a name or a balance. For lists (an invoices table, a subscriptions grid), wire the binding through the editor's Data panel, which connects your component to the live payload — open the Data tab in the component editor to set this up rather than constructing the call by hand.

What you can — and can't — put in a component

Components render in a sandboxed iframe under a Content-Security-Policy, and every saved or AI-generated component is run through a security scanner. Keep components self-contained:

  • ✅ Inline <style> and HTML markup — the normal way to build a component.

  • ✅ Live data via declared data bindings — the supported way to reach commerce data.

  • ⚠️ External scripts, stylesheets, fonts, images, iframes, and outbound network calls (fetch/XHR to other origins) are flagged by the scanner. Bundle what you need and use bindings for data instead of fetching.

  • ⚠️ Inline event handlers (onclick="…"), javascript: URLs, <base>/<meta refresh>, and similar are flagged. Avoid them.

  • 🔒 Every interpolated value is HTML-escaped, so you can't inject raw HTML through a field or binding value — by design.

The scanner is report-only today and tightens to enforcement later, so a component that's clean now stays clean.

Where a component can appear

A component shows up only where you allow it. Set these on the component:

  • Contexts — which editors / page types it's available in: Storefront, Checkout, Customer Portal, Partner Portal, CSR Portal, or Payment Pages. A component missing from a page almost always has the wrong contexts.

  • Audience roles and required permissions — narrow who can see or use it.

  • Status — it must be Active to appear as a block.

Built-in components to start from

Four code components ship seeded in the library — open any of them in the component editor to see complete, working code:

  • Pricing Card — a pricing tier with feature list and CTA (shown above).

  • Alert Banner — an info/success/warning notice (shown above).

  • Metric Dashboard — a responsive grid of KPI cards with up/down trend styling.

  • Testimonial Block — a quote with avatar initials and attribution (a textarea quote plus text fields).

Building one

  1. In Content → Component Library, click New Component (or Create with Mango to generate a first draft from a description).

  2. Paste or write your HTML + <style> in the Code view.

  3. Define your fields so authors can edit the right values, and reference them as {{name}}.

  4. Declare any data bindings and read them as {{data.<name>.field}}.

  5. Set the contexts it should appear in, mark it Active, and save.

It's now a block in the page editor everywhere its context matches.

Last updated

Was this helpful?