What selectedValues expects
selectedValues on an instanceList is a list of IDs, and every ID must already be one of the IDs in the widget's values or instanceList. The widget shows the descriptor of each matching option, so an ID that isn't among the options has nothing to display and the prompt comes up blank.
The usual causes are a single ID not wrapped in a list, an ID that isn't in values, a value assigned from an event handler, a saved value the list never loaded, and a descriptor read before anything is selected.
The value is a list of strings, so wrap single IDs
The instanceList widget's value is a list of strings, even when multiSelect is false (the default). Reading myList.value returns something like ["3aa5550b7fe348b98d7b5741afc65534"], and an empty list [] when nothing is selected. That has two consequences.
When you set a default from a single saved ID, wrap it in a list:
{
"type": "instanceList",
"id": "relationshipManager",
"label": "Relationship Manager",
"values": "<% workers.data %>",
"selectedValues": "<% !empty savedRecord.manager ? [ savedRecord.manager.id ] : [] %>"
}
When you pass one list's selection to another widget, pass the list as it is (instanceListId.value) for a multi-select target, or [ instanceListId.value[0] ] for a single one. Workday's examples guard both with ! empty instanceListId.value ? … : null, because on first load nothing is selected yet and [0] has nothing to index.
The list:toList(...) PMD function is the other common way to turn a single value into a list, and Workday's sample apps use it in selectedValues bindings.
Why the ID has to be in values first
The IDs in selectedValues must be a subset of the IDs in values or instanceList. Three ordinary situations break that rule.
The field names differ. If the endpoint's records don't have a root-level id, you set idKey (for example "idKey": "workdayID.id"). selectedValues must then contain values of that key, not the Workday ID from somewhere else in the record.
The list is filtered or reshaped. If values comes from a script that filters records or maps them to new id fields, the saved ID has to survive that mapping. A record you filtered out cannot be pre-selected.
The list is search-driven. With searchEndPoint or onSearch, the widget only preloads what values returns, often <% [] %>. A saved selection is not in that empty list, so selectedValues has nothing to match.
Use selectedValuesAndDescriptors when the saved value isn't loaded
selectedValuesAndDescriptors is the attribute for a default the list hasn't loaded. It takes objects with both id and descriptor, and Workday states it does not need to be a subset of values or instanceList. That makes it the right choice for an edit page that reopens a saved record on a search-driven prompt:
{
"type": "instanceList",
"id": "assignedWorker",
"label": "Assigned worker",
"values": "<% [] %>",
"searchEndPoint": "<% endpoints.workerSearch %>",
"searchResultValues": "<% workerSearch.data %>",
"selectedValuesAndDescriptors": "<% !empty equipmentRequest.assignedTo ? [ equipmentRequest.assignedTo ] : [] %>"
}
Here equipmentRequest.assignedTo is a reference field returned by the app's own endpoint, so it already carries id and descriptor. If yours only returns an ID, build the map yourself: [ { 'id': rec.workerId, 'descriptor': rec.workerName } ].
selectedValues is read once, at page load
You can set selectedValues and selectedValuesAndDescriptors only when the page loads the widget, not in an event handler. Assigning otherList.selectedValues = … inside an onChange does nothing useful. In a script, call the widget method instead:
orgMembersList.setValues( allOrgMembers );
orgMembersList.setValue( allOrgMembers.map(item => {item.id}) );
The two methods do different jobs:
setValues(list)replaces all the options. Each item needsidanddescriptor(or the keys named byidKeyanddisplayKey), and may carry other fields.setValue(list)sets which options are selected. Each item is an ID only, and the IDs must be in the current options.
Order matters for the same subset reason as above: when one list's onChange loads another list's options and pre-selects some of them, call setValues first and setValue second, as Workday's own example does. To clear a list, setValues([]) removes the options.
If the source data is an object rather than a list (an endpoint that returns one worker, say), wrap it: Workday's example binds "values": "<% [worker] %>" for exactly this reason.
selectedValues only applies on edit pages
The attribute reference says to use selectedValues only on edit pages. On a view page an instanceList is a display of instances, typically with wid set to turn on the related actions menu and the instance link; there is no prompt to pre-select. If the same PMD fragment is reused on a view page, populate values with just the instances you want shown.
Reading the selection back safely
value (or getValue()) returns the selected IDs; selectedEntries (or getSelectedEntries()) returns maps with id and descriptor. Both return an empty list when nothing is selected, so guard any read of the descriptor:
<% !empty assignedWorker.value ? assignedWorker.selectedEntries[0].descriptor : null %>
A widget can only be referenced by widgets that come after it in the PMD. Reference it earlier and PMD validation returns an "Undefined variable" error, which often appears when a selectedValues binding points at a list defined lower on the page.
When the selection loads but doesn't submit
Pre-selection and submission are separate. The selected IDs reach the outbound endpoint only through valueOutBinding, valuesOut or an onSend handler. Two documented rules catch people out:
- If the widget's
visibleis false on an edit page, itsvalueOutBindingandvaluesOutdata are left out of the payload. UseonSendto add the value if a hidden list must still submit. - In Workday's example,
valueOutBindingbinds the selected ID to a reference field such ascharity.relationshipManager.id. To send an array of JSON values, the binding uses theendpointName.[].fieldNamepattern; for anything more complex, build the payload inonSend.
More on these bindings is in submitting and validating Extend edit pages.
Checking the default in the logs and the saved record
Log what the widget received and what it holds, then read the record you saved. Add a console.debug call before the last line of the script (a console call can't be the last line, because the last line is the script's return value):
"onChange": "<%
var members = orgMembers.invoke({ 'id': self.value[0] }).data;
orgMembersList.setValues(members);
orgMembersList.setValue(members.map(m => {m.id}));
console.debug('selected after setValue: ', orgMembersList.value);
true;
%>"
On the Workday Developer Site, open Analytics > Logs and filter wd_category is console to see the message, or wd_category is pageError.script for a script exception. A selected list that logs [] after setValue means the IDs weren't in the options you set. Then submit the page and read the saved record back through its GET endpoint: the reference field should hold the ID you expected, not the first option or null.
When one list should drive another, the full pattern is in cascading instance lists in Extend. For page errors that appear before the widget renders at all, see Workday Extend page errors.