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), andrenders 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 atitelfield, 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:
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:
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)
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: alertType — select, 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
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/XHRto 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
textareaquote plus text fields).
Building one
In Content → Component Library, click New Component (or Create with Mango to generate a first draft from a description).
Paste or write your HTML +
<style>in the Code view.Define your fields so authors can edit the right values, and reference them as
{{name}}.Declare any data bindings and read them as
{{data.<name>.field}}.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?
