Custom Labels in Dynamic Dropdowns in SailPoint IdentityIQ Forms

:puzzle_piece: The Problem

When building custom forms in SailPoint IdentityIQ (IIQ), it’s quite common to use dynamic dropdown fields populated by objects from the identity warehouse — for example:

  • ManagedAttribute objects (groups, permissions, drives, etc.)

  • Application or Identity objects

  • Any other persisted SailPointObject type returned from a script

However, there’s a limitation in the form renderer:

Fields typed as ManagedAttribute (or any object type) always display their default displayName/name, and do not respect displayProperty / valueProperty attributes.

This becomes a problem when:

  • You need to show a localized label (sysDescriptions.de_DE)

  • You want to display a description instead of the internal name

  • You want to combine multiple attributes in the label (like name (code))

So the question is:

:face_with_monocle: How can we create a dropdown that binds to a ManagedAttribute (or other object) but shows a custom label?


:gear: The Concept — Multi-Lingual Allowed-Values Lists

The supported solution is to not use type="ManagedAttribute", but instead:

  • Set type="string"

  • Return a list of pairs [value, label] from the AllowedValuesDefinition script

This is a known pattern in IIQ called multi-lingual allowed-values lists:

result = []

for each object in queryResult:
    display = object.description 
           or object.sysDescriptions['de_DE']
           or object.displayName 
           or object.name 
           or object.id

    subList = [ object.id, display ]
    result.add(subList)

return result

  • The first element is the value saved by the form (e.g. ManagedAttribute.id)

  • The second element is the label shown to the user in the dropdown

You can use this approach for any object type (ManagedAttribute, Identity, Application, Role, etc.)


:laptop: Full Working Example

Below is a real-world snippet:
Dynamic dropdown listing all ManagedAttribute objects classified with both GDrive and the selected location.
It shows description if present, else sysDescriptions.de_DE, else fallback to displayName.

<Field
  name="gDriveItem"
  displayName="Verfügbare G-Laufwerke"
  type="string"
  dynamic="true"
  postBack="true"
  required="true"
  dependencies="selectedLocation">

  <AllowedValuesDefinition>
    <Script>
      <Source>
        import sailpoint.object.*;
        import sailpoint.object.Filter;
        import sailpoint.object.QueryOptions;
        import org.apache.log4j.Logger;

        import java.util.List;
        import java.util.ArrayList;
        import java.util.Map;
        import java.util.Collections;

        Logger log = Logger.getLogger("de.ndr.GDrivePermissionsByItem");

        if (selectedLocation == null || selectedLocation.trim().isEmpty()) {
          return Collections.EMPTY_LIST;
        }

        QueryOptions qo = new QueryOptions();
        Filter mainFilter = Filter.collectionCondition(
            "classifications",
            Filter.and(
                Filter.eq("classification.name", "GDrive"),
                Filter.eq("classification.name", selectedLocation)
            )
        );
        qo.addFilter(mainFilter);

        List gDrives = context.getObjects(ManagedAttribute.class, qo);
        if (gDrives == null || gDrives.isEmpty()) {
          return Collections.EMPTY_LIST;
        }

        List result = new ArrayList();

        for (Object o : gDrives) {
          ManagedAttribute ma = (ManagedAttribute) o;

          String display = null;
          Object desc = ma.getAttribute("description");
          if (desc != null) display = String.valueOf(desc);

          if (display == null) {
            Map sys = (Map) ma.getAttribute("sysDescriptions");
            if (sys != null && sys.get("de_DE") != null) {
              display = String.valueOf(sys.get("de_DE"));
            }
          }

          if (display == null) {
            display = (ma.getDisplayName() != null) ? ma.getDisplayName()
                      : (ma.getName() != null ? ma.getName() : ma.getId());
          }

          List subList = new ArrayList();
          subList.add(ma.getId());      // stored value
          subList.add(display);         // visible label
          result.add(subList);
        }

        return result.isEmpty() ? Collections.EMPTY_LIST : result;
      </Source>
    </Script>
  </AllowedValuesDefinition>
</Field>


:package: How to Use the Selected Value

Because the field type is string, the form will return the ManagedAttribute.id.
If you need the full object later (e.g. in workflow logic), just resolve it by ID:

ManagedAttribute ma = context.getObjectById(ManagedAttribute.class, form.getField("gDriveItem"));


:rocket: Benefits of This Pattern

  • :white_check_mark: Works with any object type (not just ManagedAttribute)

  • :white_check_mark: Full control over label format, language, fallbacks

  • :white_check_mark: Keeps form clean and dynamic (postBack, dependencies, etc.)

  • :white_check_mark: Easy to extend (add counts, concatenated metadata, …)


:light_bulb: Pro Tips

  • You can extend the label to show multiple fields, e.g. ma.getName() + " - " + ma.getAttribute("costCenter").

  • For multi-lingual support, use the sysDescriptions map (it contains translations).

  • If you want to support multiple selections, just add multi="true" to the field and handle a List<String> of IDs.


:chequered_flag: Summary

You cannot override labels for type="ManagedAttribute" fields in IIQ forms.
But by switching to type="string" and returning [id, label] pairs,
you get full control over the display text while preserving clean data binding.

This pattern is simple, reusable, and works across all object types — and it’s one of the cleanest ways to build dynamic, user-friendly dropdowns in IdentityIQ forms.

5 Likes