Collection module UI components
How to add UI components for collecting and managing payment details.
Add custom collection module UI components to create, display, and summarise payment methods in Root Dashboard and Root Embed.
These components let your collection module return HTML that Root renders inside an iframe. Use them to collect payment provider data, show payment method details, and display a compact payment method summary.
Render create payment method
The renderCreatePaymentMethod function renders the UI for creating a new payment method. When a customer in Embed or a user in Root Dashboard selects the collection module payment method, Root fetches and renders the HTML returned by this function.
Use this component to collect the payment provider data required to create a payment method. The content is rendered inside an iframe and communicates with Root using the exposed functions described below.

The renderCreatePaymentMethod function accepts a single params object.
| Parameter | Type | Required | Description |
|---|---|---|---|
policy | Object | No | Used when the payment method is being created for a policy. |
application | Object | No | Used when the payment method is being created for an application. |
policyholder | Object | No | The policyholder the payment method is being created for, when available. Root Dashboard and Embed usually pass this value in current flows, but the hook contract allows it to be undefined. |
Render functions can return the HTML string directly or return a promise that resolves to the HTML string.
// Create content to be returned in Root Dashboard and Embed.
const renderCreatePaymentMethod = (params: {
application?: Record<string, any>;
policyholder?: Record<string, any>;
policy?: Record<string, any>;
}) => {
return `<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div id="payment-element"></div>
<script></script>
</body>
</html>`;
};Create payment method lifecycle
Root exposes helper functions inside the iframe so your collection module can submit the payment method form and pass the result back to Root.
You implement submitCreatePaymentMethod. Root injects completeCreatePaymentMethod, setIsLoading, and setIsValid into the iframe. Call these injected functions, but do not redefine them.
The create payment method flow is:
- Root renders the HTML returned by
renderCreatePaymentMethod. - The user completes the payment method form.
- Root calls
submitCreatePaymentMethodwhen the user submits the form. - Your module validates or submits the payment data to the payment provider.
- Your module calls
completeCreatePaymentMethod(result). - Root invokes the collection module's
createPaymentMethodhook with the submitted result. - The
createPaymentMethodhook returns the persisted payment method shape.
The examples below are partial examples. They assume you have already initialized your payment provider SDK and mounted the provider payment element. The examples use Stripe, but the same pattern works with any payment provider.
The examples name the submitCreatePaymentMethod argument params, but they do not depend on it. Use the payment provider state available inside your iframe, such as the mounted provider element.
Submit create payment method
The submitCreatePaymentMethod function is called when the user selects the submit button in Root Dashboard or Embed. Use it to run the payment provider submission code in your collection module.
const renderCreatePaymentMethod = (params: {
application?: Record<string, any>;
policyholder?: Record<string, any>;
policy?: Record<string, any>;
}) => {
return `<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div id="payment-element"></div>
<script>
const submitCreatePaymentMethod = (params) => {
return stripe.confirmSetup({
elements,
redirect: "if_required",
});
};
</script>
</body>
</html>`;
};
Complete create payment method
Within submitCreatePaymentMethod, call completeCreatePaymentMethod to pass the result from your payment provider code back to Root. Root then invokes the collection module's createPaymentMethod hook to finalise payment method creation.
Do not override
completeCreatePaymentMethod. Doing so will break the payment process.
const renderCreatePaymentMethod = (params: {
application?: Record<string, any>;
policyholder?: Record<string, any>;
policy?: Record<string, any>;
}) => {
return `<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div id="payment-element"></div>
<script>
const submitCreatePaymentMethod = (params) => {
return stripe
.confirmSetup({
elements,
redirect: "if_required",
})
.then(function (result) {
// Pass the provider result to Root so it can call the createPaymentMethod hook.
return completeCreatePaymentMethod(result);
});
};
</script>
</body>
</html>`;
};Set loading state
The setIsLoading function controls the loading state while submitCreatePaymentMethod runs. Use it to reset the Root loading state after an error or when the provider submission flow completes.
Do not override
setIsLoading. Doing so will break the loading functionality.
const renderCreatePaymentMethod = (params: {
application?: Record<string, any>;
policyholder?: Record<string, any>;
policy?: Record<string, any>;
}) => {
return `<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div id="payment-element"></div>
<script>
const submitCreatePaymentMethod = (params) => {
return stripe
.confirmSetup({
elements,
redirect: "if_required",
})
.then(function (result) {
if (result.error) {
setIsLoading(false);
}
return completeCreatePaymentMethod(result);
});
};
</script>
</body>
</html>`;
};Set validation state
The setIsValid function controls whether the submit button is enabled. Call setIsValid(false) while the form is incomplete, then call setIsValid(true) when the user can submit the payment method.
Do not override
setIsValid. Doing so will break the validation functionality.
const renderCreatePaymentMethod = (params: {
application?: Record<string, any>;
policyholder?: Record<string, any>;
policy?: Record<string, any>;
}) => {
return `<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div id="payment-element"></div>
<script>
paymentElement.on("change", (event) => {
setIsValid(event.complete);
});
const submitCreatePaymentMethod = (params) => {
return stripe
.confirmSetup({
elements,
redirect: "if_required",
})
.then(function (result) {
if (result.error) {
setIsLoading(false);
}
return completeCreatePaymentMethod(result);
});
};
</script>
</body>
</html>`;
};Render payment method details
The renderViewPaymentMethod function creates a custom view for the payment method created through renderCreatePaymentMethod. Root displays this view on policy and application pages.
This component is optional. If you do not define it, Root displays the module data inside the payment method.

The renderViewPaymentMethod function accepts a single params object.
| Parameter | Type | Required | Description |
|---|---|---|---|
policy | Object | No | Used when the payment method view is for a policy object. |
application | Object | No | Used when the payment method view is for an application object. |
policyholder | Object | No | The policyholder assigned to the payment method, when available. Root Dashboard and Embed usually pass this value in current flows, but the hook contract allows it to be undefined. |
payment_method | Object | Yes | The collection module payment method object that the view displays. |
// Payment method view content to be returned in Root Dashboard.
const renderViewPaymentMethod = (params: {
application?: Record<string, any>;
policyholder?: Record<string, any>;
policy?: Record<string, any>;
payment_method: Record<string, any>;
}) => {
const { payment_method, policy } = params;
return `<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<table id="payment-details">
<tr>
<th>Type</th>
<td>Collection module</td>
</tr>
<tr>
<th class="no-background">Key</th>
<td class="key">${payment_method.collection_module_key}</td>
</tr>
<tr>
<th>Id</th>
<td>${payment_method.module.id}</td>
</tr>
<tr>
<th>Payment method</th>
<td>${payment_method.module.payment_method}</td>
</tr>
<tr>
<th>Billing day</th>
<td>${policy && policy.billing_day}</td>
</tr>
<tr>
<th>Livemode</th>
<td>${payment_method.module.livemode}</td>
</tr>
<tr>
<th>Status</th>
<td>${payment_method.module.status}</td>
</tr>
<tr>
<th>Usage</th>
<td>${payment_method.module.usage}</td>
</tr>
</table>
<img class="stripe-logo" />
</body>
</html>`;
};
Render payment method summary
The renderViewPaymentMethodSummary function creates a compact view for the payment method created through renderCreatePaymentMethod. Root displays this summary when a user opens the edit payment method view or creates a new payment method in Root Dashboard.
Use this component to show the existing payment method for the policyholder in a simplified card.

The renderViewPaymentMethodSummary function accepts a single params object.
| Parameter | Type | Required | Description |
|---|---|---|---|
policy | Object | No | Used when the payment method summary is for a policy. |
application | Object | No | Used when the payment method summary is for an application. |
policyholder | Object | No | The policyholder assigned to the payment method, when available. Root Dashboard and Embed usually pass this value in current flows, but the hook contract allows it to be undefined. |
payment_method | Object | Yes | The collection module payment method object that the summary displays. |
// Payment method summary content to be returned in Root Dashboard.
const renderViewPaymentMethodSummary = (params: {
application?: Record<string, any>;
policyholder?: Record<string, any>;
policy?: Record<string, any>;
payment_method: Record<string, any>;
}) => {
const { application, payment_method, policy, policyholder } = params;
return `<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div id="payment-details">
<h3>Stripe payment method</h3>
<div id="card-details">
Card: ${payment_method.module.card?.brand || 'Unknown'} **** **** **** ${
payment_method.module.card?.last4 || 'Unknown'
}, Expires: ${payment_method.module.card?.exp_month || 'Unknown'}/${payment_method.module.card?.exp_year || 'Unknown'}
</div>
<div class="api-attributes-wrapper">
<div class="api-attributes" id="policy-id">Policy ID: ${policy && policy.policy_id}</div>
<div class="api-attributes" id="application-id">Application ID: ${
application && application.application_id
}</div>
<div class="api-attributes" id="policyholder-id">Policyholder ID: ${
policyholder && policyholder.policyholder_id
}</div>
</div>
</div>
</body>
</html>`;
};Troubleshooting
Submit button stays disabled
Confirm that your payment provider element calls setIsValid(true) when the form is complete. If setIsValid(false) is called and never reset, Root keeps the submit button disabled.
Loading state does not reset
Call setIsLoading(false) when the payment provider returns an error or when your submission flow ends without creating a payment method. Do not override Root's setIsLoading function.
Payment method is not created
Confirm that submitCreatePaymentMethod calls completeCreatePaymentMethod(result) with the provider result. Root uses this call to invoke the collection module's createPaymentMethod hook.
Iframe content does not render
Verify that each render function returns a complete HTML string with <html>, <head>, and <body> tags. Also check that the returned string does not contain syntax errors in the embedded script.
Render update payment method
The renderUpdatePaymentMethod function renders the UI for editing a payment method that already exists. The Root dashboard fetches and renders this HTML when a user opens the edit payment method view on a policy or an application.
Use this component when the details behind a payment method can change without the payment method being replaced, such as a re-tokenised card, a corrected account number, or a new mandate reference.
This component is optional. If you do not define it, the Root dashboard displays the payment method's module data and the user can still change the billing day.
The renderUpdatePaymentMethod function accepts a single params object.
| Parameter | Type | Required | Description |
|---|---|---|---|
payment_method | Object | Yes | The collection module payment method object being edited. Use payment_method.module to pre-populate the form. |
policy | Object | No | Used when the payment method is being edited on a policy. |
application | Object | No | Used when the payment method is being edited on an application. |
policyholder | Object | No | The policyholder assigned to the payment method, when available. |
The update component uses the flow-neutral helper names
submitRenderPaymentMethodandcompleteRenderPaymentMethod. The create-specific namessubmitCreatePaymentMethodandcompleteCreatePaymentMethodhave no effect here.
// Update content to be returned in Root Dashboard.
const renderUpdatePaymentMethod = (params: {
payment_method: Record<string, any>;
application?: Record<string, any>;
policyholder?: Record<string, any>;
policy?: Record<string, any>;
}) => {
const { payment_method } = params;
const { module } = payment_method;
// Escape `</` in interpolated values so a payload containing markup cannot break out of the
// element it is rendered into.
const accountNumber = String(module.account_number || "").replace(/<\//g, "<\\/");
return `<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<form id="update-form">
<label for="account-number">Account number</label>
<input id="account-number" name="account_number" value="${accountNumber}" />
</form>
<script>
const input = document.getElementById("account-number");
setIsValid(Boolean(input.value));
input.addEventListener("input", () => setIsValid(Boolean(input.value)));
const submitRenderPaymentMethod = () => {
completeRenderPaymentMethod({ account_number: input.value });
};
</script>
</body>
</html>`;
};Update payment method lifecycle
The update flow uses the same injected helpers as the create flow, and routes the result to the collection module's updatePaymentMethod hook instead of createPaymentMethod.
The update payment method flow is:
- Root renders the HTML returned by
renderUpdatePaymentMethod. - The user edits the payment method form.
- Root calls
submitRenderPaymentMethodwhen the user saves. - Your module validates the input, or submits it to the payment provider.
- Your module calls
completeRenderPaymentMethod(data). - Root invokes the collection module's
updatePaymentMethodhook with thatdata. - The
updatePaymentMethodhook returns the payment method's newmodulepayload, which Root persists.
Update payment method hook
The updatePaymentMethod hook receives the payment method being edited, its policyholder, and the data your iframe passed to completeRenderPaymentMethod.
| Parameter | Type | Required | Description |
|---|---|---|---|
payment_method | Object | Yes | The payment method as it is stored before the update. |
policyholder | Object | Yes | The policyholder the payment method belongs to. |
data | Object | Yes | The object your iframe passed to completeRenderPaymentMethod. |
The
moduleobject you return replaces the stored payload; Root does not merge it for you. Spread the existingpayment_method.moduleif you only mean to change some of its fields.
const updatePaymentMethod = (params: {
payment_method: Record<string, any>;
policyholder: Record<string, any>;
data: Record<string, any>;
}) => {
const { payment_method, data } = params;
return {
module: {
...payment_method.module,
...data,
},
};
};Two constraints apply to updates:
- A payment method on an archived collection module cannot be updated, because the update runs the module's code.
- A production payment method cannot be updated against the draft version of a collection module. Publish the version, or test the change against a sandbox payment method.
Updated 15 days ago