We’ve migrated our documentation to a new site, which means some URLs have changed.
Subscriptions

How do I create, display, and customize custom forms and inline templates in Piano Identity Management?

1. Creating a custom form template

There are two custom form templates in the Piano dashboard:

  • Experience → Piano ID → Custom Form (newer; supports variants, still evolving).

  • Piano ID → Custom Form

For day-to-day work, use the older one. Use the newer one when you need variants (multiple visual flavors of the same form) and have validated the behavior in sandbox first.

Steps

  1. Dashboard → Experience → Piano ID → Add Template, then select Custom Form as the base.

  2. In the HTML, include the tag — this is the placeholder where the fields configured on your custom form (under Manage → Custom Fields → Add Form) render.

  3. Add HTML around for headings, helper text, dividers, etc.

  4. Edit the CSS tab for per-form styling. Note: custom-form CSS loads after the Piano ID Layout CSS, so you can override defaults — use higher specificity or !important if needed.

If your custom form template does not show up in the Composer "Display Form / Show Template" dropdown, the most common cause is a missing tag in the HTML. Add it, save, then re-open the dropdown.

2. Two ways to display a custom form

From a Composer experience

Drop a Display Form or Show Template card into your Composer experience and select the form / template. Composer passes the user's session through automatically.

From JavaScript with tp.pianoId.showForm

Call directly from page code:

tp.pianoId.showForm({
  formName: "FormName",        // the form configured under Manage → Custom Fields → Add Form
  templateId: "XXXXX",         // optional — pick a specific template
  variantId: "XXXXXX"          // optional — pick a specific variant
});

formName controls which fields and validation rules apply. templateId and variantId control which look is used.

Showing a form after checkout

A common pattern is to show a profile-completion form after a successful checkout. Add a checkoutComplete handler in your Composer Integration Script that calls tp.pianoId.showForm with the right formName and templateId. The form will render in the modal once the checkout closes.

Different styles for different placements

To make the same custom form look different in different contexts, create variants of the template:

  1. Add a class to the HTML elements you want to style differently.

  2. Define a CSS property for that class in the template's CSS tab.

  3. Define a content field to back each CSS property value.

  4. Bind each content field to its CSS property.

  5. Create a new variant and override the content field values for that variant.

showForm({variantId: ...}) then renders the form with the variant-specific styling, leaving other forms using the same template untouched.

3. Customizing form CSS — global vs. per-form

Two layers of CSS apply to any Identity Management form:

  • Piano ID Layout (global): styles every Identity Management template including login, register, custom forms, and My Account. Use this for theme-wide changes (font family, primary button color, etc.).

  • Custom Form template (per-form): styles only that form. Loads after Layout, so it overrides cascade-equal defaults. Use this for form-specific tweaks.

Example — global font and primary button color in Piano ID Layout CSS:

.piano-id-modal input { font-family: "Open Sans", sans-serif; }
.piano-id-modal .btn-primary { background: #c20000; }

For one specific form, duplicate the template (or create a new one) under Experience → Piano ID → Add Template and use that template's CSS tab for the rule. In your Composer experience configuration, pick this new template in the "Display Form / Show Template" option.

Custom field display layout

To change how questions and answers are arranged (for example, the question above a dropdown, with options inside the dropdown), add to Piano ID Layout CSS:

multi-select-custom-field,
single-select-custom-field,
text-custom-field,
number-custom-field,
boolean-custom-field,
scale-custom-field,
date-custom-field {
  display: flex;
  flex-direction: column-reverse;
}
.input, .custom-field-input { margin-top: 0px !important; }
.input.custom-field-input,
select.custom-field-input,
input[type="text"].custom-field-input,
input[type="number"].custom-field-input,
input[type="date"].custom-field-input,
input[type="password"].custom-field-input,
.pn-datepicker__toggle input,
.selectbox-wrapper .input { padding-left: 1em !important; }
.custom-field { padding-top: 1em; }
.custom-field-label {
  width: calc(100% - 2.5em) !important;
  padding: 0.5em 0.5em 0.5em 2em !important;
  color: black !important;
  top: 0px !important;
  position: relative !important;
  white-space: normal !important;
  text-overflow: initial !important;
  text-transform: initial !important;
}
.custom-field-label::before { top: 0.7em !important; left: 0.1em !important; }

Two practical limits: the dropdown UI caps the visible-options height at three rows (this cannot be increased), and checkboxes don't natively support multi-select rendering without scrolling. If multi-select-no-scroll matters, use the rule under "Multi-select field display" in the input-fields article.

Phone-number / country-code dropdowns

If a Phone Number field's country-code dropdown displays incorrectly, the CSS in the form template or the inherited Piano ID Layout is usually the cause. Confirm CSS syntax (spaces around {), confirm which template the form actually uses, and remember that changes to Piano ID Layout affect every form sharing it (registration, custom forms, but not necessarily purchase forms if they use a different layout).

4. Removing the First Name / Last Name fields from registration

To remove the First and Last name inputs from the Piano ID Register Page template, delete or comment out the following blocks in the template's HTML:

HTML
<p class="input-group with-icon">
  <input fieldRegisterFirstName type="text" value="">
  <span class="placeholder"><i class="icon icon-user"></i><t>first name</t></span>
  <span showIfFirstNameInvalid class="error-message"><t>First name is required</t></span>
  <span showIfFirstNameInvalidBy="SERVER_ERROR" class="error-message" setFirstNameServerErrorMessage></span>
</p>
<p class="input-group with-icon">
  <input fieldRegisterLastName type="text" value="">
  <span class="placeholder"><i class="icon icon-user"></i><t>last name</t></span>
  <span showIfForLastNameInvalid class="error-message"><t>Last name is required</t></span>
  <span showIfLastNameInvalidBy="SERVER_ERROR" class="error-message" setLastNameServerErrorMessage></span>
</p>

before doing this, also make sure first and last name are not marked required in your Identity Management settings — otherwise, the backend will still reject the registration even though the inputs are gone.

For more selective removal (for example, only on a specific registration flow), see section 8 on stage directives instead.

Editing best practice — delete, don't comment

When trimming a custom form template, delete unwanted element blocks rather than commenting them out. Commented-out blocks have been observed to interfere with Identity Management template rendering. After deleting, save as a new version and switch the experience to that version.

5. Inline templates — embedding Identity Management screens in a page

By default, Identity Management screens (login, register, custom form) render in a modal. To embed them directly in a page (no modal), use inline display mode:

tp.pianoId.show({
  displayMode: 'inline',
  screen: 'register',
  containerSelector: '#login-form',
  loggedIn: function(data) {
    console.log('user', data.user, 'logged in with token', data.token);
  },
  loggedOut: function() {
    console.log('user logged out');
  }
});

containerSelector is the CSS selector for the page element that will host the embedded template. Make sure that container exists in the HTML at the time show() runs.

Using a specific template for the inline render

Pass templateId to use a customized register page template rather than the system default:

tp.pianoId.show({
  displayMode: 'inline',
  screen: 'register',
  containerSelector: '#login-form',
  templateId: 'OTBLGJMPK55J',
  loggedIn: function(data) { /* ... */ },
  loggedOut: function() { /* ... */ }
});

Replace OTBLGJMPK55J with the actual template ID from your customized Piano ID register page template (found under Manage → Templates in the Experience > Piano ID category).

Inline custom login template

You can also use the inline pattern for a custom register template that visually serves as an inline login experience — useful when you want to embed a login form on a page with publisher-specific text and styling:

tp.pianoId.show({
  displayMode: 'inline',
  screen: 'register',
  containerSelector: '#login-form',
  templateId: 'YOUR_CUSTOM_TEMPLATE_ID'
});

Setting up the inline container

Inline templates need a target container in your HTML. The setup is just an empty element with the matching id / class:

HTML
<div id="login-form"></div>

…and then the tp.pianoId.show({...}) call referencing containerSelector: '#login-form'.

Standalone inline registration page

To deliver a standalone inline registration page (not a hybrid login/register modal):

  1. Build a registration-specific template under Manage → Templates that does not default to a login state.

  2. In Composer, create an experience and use the Show template action to trigger your new template; configure it to bypass any login screen.

  3. If the template doesn't appear, check for conflicting Composer experiences and disable the others temporarily to isolate. Verify the template is listed in the Composer template menu.

After-login behavior

If a registration form keeps appearing after login because it is configured inline, a page refresh is required for the form to disappear. Also confirm browser extensions (Adblock, etc.) are not blocking the post-registration content — they sometimes interfere with what renders inside the inline container.

6. Multi-step registration

A multi-step registration in Identity Management typically maps to the following templates:

Step

Template

Customer information input

Piano ID Layout (the wrapper)

Authentication / double opt-in

Piano ID email confirmation required, Piano ID email is not confirmed, Piano ID email confirmation view

Payment information input

Checkout

Confirmation

Confirmation Screen

Registration completion

Checkout

When designing a multi-step registration form:

  • Include every required field — missing fields like Name, Surname, Email address can produce errors like "not set access token cookie".

  • If buttons / links don't work in the dashboard preview, that's expected — preview mode disables navigation between steps. Test in sandbox or live.

  • If older templates cause unexpected functionality issues, reset to default to pick up current behavior, then re-apply only the customizations you need.

  • Centralize repeated HTML / CSS into the Piano ID Layout so you don't duplicate across step templates.

7. Inline registration containers vs. modal — picking the right pattern

Pattern

When to use

Modal (default)

Standard sign-up flow, contextual gating, paywall conversion.

Inline (displayMode: 'inline')

Dedicated /register page, newsletter sign-up embedded in an article, multi-form layouts on a profile page.

Composer Show Template card

Whenever you want the experience targeting / scheduling features to apply.

For inline, you must own the container element in your HTML. For Composer-driven modal, Composer manages the lifecycle.

8. Integrating custom registration forms (modal context)

When invoking a custom registration template inside a modal (for example, opening from a page button), the JavaScript path is tp.pianoId.show() or tp.pianoId.showForm(). The common pitfall is scopetp is only available in the page where the Piano integration script ran. If you launch a modal whose JavaScript runs in a sub-scope where tp is not defined, the function call fails silently.

Two fixes:

  • Initialize the modal launcher in the same scope as your Piano integration script so tp is in scope.

  • If you can't, pass a reference to tp (or call a wrapper function defined in the parent scope) from the modal's context.

The same tp.pianoId.show() / tp.pianoId.showForm() you would use anywhere is what powers a custom modal launch.

9. Integrating a custom form template — common pitfalls

When wiring a custom form into a Piano template, the recurring issues are:

  • Use , not for Identity Management-aware scripts. Identity Management processes the tag specifically; the div variant is for non-ID templates.

  • Load JavaScript libraries explicitly (jQuery, AngularJS, etc.) if your script depends on them. If the library is undefined inside the Identity Management iframe, rewrite using vanilla JavaScript or load the library inside the template.

  • Avoid AngularJS-style template expressions like {{user.email}} if AngularJS is not the rendering engine for that template. Identity Management templates render with AngularJS 1.x, but expressions in unexpected contexts can be silently passed through as literal text.

  • Watch console errors — unexpected EOF or undefined property errors can stop the form from rendering. Test in a sandbox copy of the template, fix until clean, then deploy.

Conventional debugging workflow: create a test variant or test template; iterate until the form renders correctly; then promote.

10. Custom checkout form — keep custom-field code in the registration template

A "custom checkout form" in Identity Management is the registration template with extra custom fields, shown inside the checkout flow. If custom fields stop showing during checkout, the most common cause is that the field-rendering code has been removed from the Piano ID registration template. Re-add the relevant or markup, then test the checkout flow in staging.

11. Inline registration showing only custom fields — fix

If your inline registration shows only custom fields (no First Name / Last Name / Email / Password), the Show Form Composer card is configured to display a form that excludes the standard registration fields. Two fixes:

  • Switch the Composer card to a template that includes the standard fields, or

  • Add specific custom fields manually to your custom template with:

html  

After the user registers, refresh the page so the inline form disappears as expected.

12. Implementing radio buttons in a custom form

Identity Management custom forms do not ship native radio buttons. The supported workaround is to style checkboxes to look like radio buttons and use JavaScript to enforce single-select behavior.

Styling

Use CSS in the Piano ID Layout or custom form template to give checkboxes a circular appearance.

JavaScript — enforce single-select

setTimeout(function() {
  var clickLabel = document.querySelectorAll('.field-checkbox-label');
  var checkboxControl = document.querySelectorAll('.field-checkbox-label checkbox-control[role="checkbox"]');
  var checkboxes = document.querySelectorAll('.field-checkbox-label input[type="checkbox"]');
  clickLabel.forEach(function(checkbox) {
    checkbox.addEventListener('click', function() {
      checkboxControl.forEach(function(checkEvent) {
        checkEvent.ariaChecked = false;
        checkEvent.classList.remove("checked");
        checkboxes.forEach(function(inputCheck) {
          if (inputCheck !== checkbox) {
            inputCheck.checked = false;
          }
        });
      });
    });
  });
}, 200);

For native radio-button support (with custom behavior beyond this pattern), reach out to your Account Manager — that work is typically a billable engagement.

13. Auto-focus, placeholders, and caching gotchas

  • Auto-focus removes placeholders. If the cursor jumps to the first field on load, the placeholder disappears immediately. To keep placeholders visible until the user types, add to the custom form template:

html setTimeout(function() { document.querySelectorAll('.custom-field-input').forEach(function(ele) { ele.classList.remove('empty'); }); }, 1000);

Adjust the timeout so it runs after form rendering.

  • iFrame padding — adjust the iframe-internal padding in Piano ID Layout CSS; you can't reach into the iframe from the parent site's CSS.

  • Caching — custom form templates have their own cache. When changes don't appear, do a Ctrl + Shift + R hard refresh, switch to a different browser, or use an incognito window.

14. Environment-specific templates and migration

Custom form templates are environment-specific. A template created in sandbox is not automatically available in production — there is no direct retrieval mechanism. To promote a sandbox template to production, contact Piano support for migration. Always test in sandbox first.

15. Multiple "Piano ID register page" templates — pick the right one

Some accounts have two Piano ID register page templates listed. They are both system-generated and neither should be deleted. For text and field changes, use the template categorized under Piano ID (the standard, longer-lived one). The other, under Experience > Piano ID, supports variants but is still evolving; use it when you specifically need variants.

If you customize the wrong one and the changes don't appear in production, switch to the other and re-apply.

16. Customizing system templates — clone, don't fight

Piano's system templates (the register page, the layout, etc.) are intentionally locked against injecting custom Handlebars variables like {{params.url}}. To implement custom logic (for example, redirect users to the origin page after registration):

  • Clone the system template via Manage → Templates → Add Template, choose the Registration use case, and use this cloned template instead.

  • Alternatively, listen for the Registration Success event in your Composer Integration Script and implement the redirect there — no template clone required.

For invoking a cloned template from page code, use:

tp.template.show({ templateId: '<template_id>' });

Or wire a Show Template card in a Composer experience.

CSS for system templates lives in the separately named Piano ID Layout template (not in the register page template itself).

Last updated: