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

How do I configure and display custom fields in Piano Identity Management templates?

1. Two ways to render custom fields in a template

Identity Management custom fields can be injected into a template in two ways:

  • auto-renders every field that is configured on the form associated with the template (for example, all fields on the My Account Fields form for the My Account profile, or all fields on the registration form for the register page). Order is controlled by Piano's default sorting rules.

  • places a single named field exactly where you put it in the HTML. This gives you control over order, grouping under section titles, and any per-field styling.

You can mix the two — see the ordering section below.

2. Creating a custom field and putting it on a form

  1. Go to Manage → Custom Fields in the Piano Dashboard.

  2. Create the field (or verify an existing one). Each field needs a unique technical ID. Mark it as read-only if subscribers should not be able to edit it themselves.

  3. Add the field to at least one form (for example, the standard M yAccount form). A field that is not assigned to any form will not render.

  4. In the template (for example, Piano ID profile in My Account), either rely on the auto-render or place a tag.

If the new field does not appear, check that:

  • The field is assigned to the correct form.

  • The template you are editing is the one actually being served (sandbox vs. production, system template vs. cloned template).

  • The template HTML does not have the relevant section commented out — commented-out blocks are a common cause of fields disappearing in one environment but not another (a copy-paste from a working environment usually resolves it).

3. Showing custom fields in two columns

By default, custom fields on the Identity Management register screen render in one column, stacked vertically. To render them in a two-column grid, add CSS to the Piano ID Layout template's CSS tab:

.custom-fields {
  display: flex !important;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-between;
}
.custom-field {
  width: 48%;
  margin: 15px 0px;
}

Substitute justify-content: space-around for space-between for slightly looser column distribution.

4. Enforcing custom field order

By default, in the My Account profile, completed custom fields move to the top of the list dynamically — so the order users see depends on which fields they've filled in. This is often surprising.

To enforce a strict order regardless of completion state, hard-code the fields in the Piano ID profile in My Account template:

XML
<custom-field fieldName="field1"></custom-field>
<custom-field fieldName="field2"></custom-field>
<custom-field fieldName="field3"></custom-field>

Replace field1, field2, etc. with the actual field technical IDs.

Mixing hard-coded and auto-sorted fields

You can hard-code only some fields and let the rest auto-sort:

  • Hard-coded first, then auto — pin specific fields at the top, then let the rest fill in:

HTML
<custom-field fieldName="field1"></custom-field> <!-- hard coded -->
<custom-fields></custom-fields> <!-- automatic -->
  • Auto first, then hard-coded — let auto-fields render at the top, with pinned fields at the bottom:

HTML
<custom-fields></custom-fields> <!-- automatic -->
<custom-field fieldName="field1"></custom-field> <!-- hard coded -->

When all fields are hard-coded, the auto block becomes redundant and can be removed (or commented out).

5. Misaligned fields — reset Layout to default

If single-select custom fields appear shifted down while text fields remain centered (a pre-selection display offset problem), the root cause is outdated CSS in the Piano ID Layout template. The fix is to reset Layout to default, which migrates the template to the current version. The new default uses a framed view that aligns single-select fields correctly with text fields.

6. Missing custom fields — check for incompatible directives

If custom fields configured on the MyAccountFields form stop rendering for logged-in users — even though they're set up and saved at registration — the cause is often that the template has been edited to use Angular 2+ syntax (*ngIf, *ngFor) inside a Identity Management template, which runs on AngularJS (1.x).

The mismatch produces a silent failure — no console error, the affected elements just don't render.

Fix: replace Angular 2+ directives with AngularJS equivalents:

Use this (AngularJS)

Not this (Angular 2+)

ng-if="..."

*ngIf="..."

ng-show="..."

*ngFor="..."

ng-repeat="..."

[binding] / (event)

Example:

HTML
<!-- Before -->
<div *ngIf="condition">
  <!-- custom fields -->
</div>

<!-- After -->
<div ng-if="condition">
  <!-- custom fields -->
</div>

Save, republish, and verify the My Account profile.

7. Custom field component DOM changes break custom scripts

Piano periodically updates the custom field component (for example, adding a new for screen readers). If your selects specific spans by index or position, the update can shift the targets and silently break your customization (including consent checkboxes).

Mitigation:

  • Track Piano release notes — when a DOM change ships, adjust selectors.

  • Use content-based or attribute-based selectors rather than positional ones (e.g., querySelector('span[role="..."]') rather than children[2]).

  • After every Piano release, smoke-test customized registration / profile screens.

  • If a release silently breaks consent capture, identify and filter the affected users for compliance review.

8. Displaying a custom field value (e.g., gift code) on the profile

To show a custom field's current value in the Piano ID profile in My Account template:

  1. Create / verify the custom field under Manage → Custom Fields. Set it read-only so subscribers can't edit it. Add it to the standard MyAccount form.

  2. Render it. Either rely on the default block, or manually:

HTML
<div class="tp-form form-horizontal">
  <custom-field fieldName="gift_code">
    <ng-template let-field>
      <div class="control-group">
        <label class="control-label" customFieldLabel [fieldName]="field.field_name"></label>
        <div class="controls" *ngIf="field.value">
          <span class="gift-code-value"></span>
        </div>
      </div>
    </ng-template>
  </custom-field>
</div>

Style the value with CSS as needed.

Then, Populate the field after a relevant event (for example, after a successful checkout, write the promotionId into the custom field):

JavaScript
tp.push(["addHandler", "checkoutComplete", function(conversion) {
  if (conversion.promotionId) {
    fetch('https://sandbox.piano.io/id/api/v1/identity/userinfo?aid=' + tp.aid +
          '&access_token=' + tp.pianoId.getToken() + '&lang=en_US', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json; charset=UTF-8' },
      body: JSON.stringify({
        uid: tp.pianoId.getUser().uid,
        form_name: 'MyAccount',
        custom_field_values: [{ field_name: 'gift_code', value: conversion.promotionId }]
      })
    });
  }
}]);

Adjust the host (sandbox vs. production), lang, and value source.

And finally: Test in sandbox — confirm the PUT call writes the expected value, then confirm the My Account template renders it.

Reading a custom field value from another template (e.g., ribbon, paywall modal)

Use tp.pianoId.loadExtendedUser on the frontend to fetch the current user's custom field values, then store the value as a Custom Variable. Reference that Custom Variable from the consuming Piano template (ribbon, modal, etc.) to display the value dynamically.

9. Special-case fields

Billing / print address

Billing and print address fields are linked to subscriptions in Management + Billing and need special handling — they typically render through their own template sections and are not interchangeable with arbitrary custom fields. Test changes in sandbox before deploying.

State / Province field — text input for most countries

In Piano's address form template, the State / Province field renders as a text input rather than a dropdown for most countries, including Italy. This is expected behavior: Piano supports detailed delivery zones (dropdown with predefined options) primarily for US and Canada, where these zones drive tax rules. Most other countries use a single tax rate, so no enumerated list is enforced. If you need predefined options for an additional country, raise it as feature feedback through your Account Manager.

10. Excluding values from dropdowns (e.g., sanctioned countries)

Piano does not currently expose a native setting to remove specific countries from the country dropdown. Two unsupported but workable approaches:

CSS-only (unreliable across browsers)

option[value="BannedCountryValue"] { display: none; }

Some browsers ignore display: none on elements and still render the entry — verify in every target browser before relying on this.

JavaScript removal (more reliable)

Remove the option from the DOM on form load. Add this as a or in the integration script:

HTML
<script>
document.addEventListener("DOMContentLoaded", function() {
  var bannedOption = document.querySelector('select#yourCustomField option[value="BannedCountryValue"]');
  if (bannedOption) {
    bannedOption.remove();
  }
});
</script>

Replace yourCustomField with the field's actual id (or use a more specific selector) and BannedCountryValue with the actual country code. Repeat the call for each blocked country, or iterate over an array.

11. Make name fields display-only in the My Account profile

If you want the First Name and Last Name to be visible but not editable in the profile, two coordinated steps are required in the Piano ID profile in My Account template:

Step 1 — Add pointer-events: none and a stable id

In the existing first / last name inputs:

HTML
<label class='piano-field'>
  <t>First name</t>
  <input class='slate-input' fieldProfileFirstName autocomplete='given-name'>
</label>
<label class='piano-field'>
  <t>Last name</t>
  <input class='slate-input' fieldProfileLastName autocomplete='family-name'>
</label>

Add style="pointer-events: none;" and an id:

HTML
<label class='piano-field'>
  <t>First name</t>
  <input class='slate-input' fieldProfileFirstName autocomplete='given-name' style="pointer-events: none;" id="given-name">
</label>
<label class='piano-field'>
  <t>Last name</t>
  <input class='slate-input' fieldProfileLastName autocomplete='family-name' style="pointer-events: none;" id="family-name">
</label>

Step 2 — Disable the inputs in a custom script

At the end of the same template, add:

XML
<custom-script>
var interval = setInterval(function() {
  if (document.getElementById("given-name") != null) {
    clearInterval(interval);
    document.getElementById("given-name").disabled = true;
    document.getElementById("family-name").disabled = true;
  }
}, 50);
</custom-script>

With both steps applied, users see the name values but cannot edit them.

Last updated: