The value is an array of id references
A MULTI_INSTANCE field on an Extend business object holds a list of references to instances of its target, which can be another business object in the same app or a Workday-delivered object such as WORKER. The View Business Objects Available to Extend report in your tenant shows which Workday-delivered objects support target and the attribute name to use. In a POST or PATCH body you send it as a JSON array of objects, each with an id; the sample apps' own comments describe the entries read back from the field as an id plus a descriptor.
A definition, using an Equipment Request that can list several reviewers:
{
"id": 7,
"name": "reviewers",
"type": "MULTI_INSTANCE",
"label": "Reviewers",
"target": "WORKER"
}
And a create request that sets it:
curl -X POST "https://api.workday.com/apps/{appId}/v1/equipmentRequests" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Laptop refresh",
"reviewers": [
{ "id": "{workerWid1}" },
{ "id": "{workerWid2}" }
]
}'
Workday's request examples for a single-instance field show a reference ID in Type=Value form, such as "relationshipManager": {"id":"Employee_ID=21001"} (Reference: Business Object API Requests). The multi-instance samples below take their ids from an earlier response instead, such as the id of a record just created.
Why a PATCH has to carry the whole list
Workday's PATCH example sends only the fields being changed (Reference: Business Object API Requests). Workday documents no operation that adds or removes a single entry of a multi-instance field. The sample apps treat the array you send as the field's new value. In Create a Work Event, registering for an event does two things in order:
- POST a new
Registrantinstance. - PATCH the
WorkEventwithregistrantsset to the current list plus the new registrant.
The second step's onSend builds the full list before sending it:
{
"name": "updateEvent",
"baseUrlType": "app",
"url": "<% 'workEvents/' + event.id %>",
"httpMethod": "PATCH",
"authType": "sso",
"onSend": "<%
let current = event.registrants ?? [];
let all = current.add({'id': createRegistrant.id});
return {'registrants': all};
%>"
}
event is the inbound endpoint that read the work event when the page loaded, and createRegistrant is the earlier outbound endpoint, whose response supplies the new id. The sample guards with ?? [] so the add still works when the field has no value yet.
Build the complete list and send it, as the samples do. Do not send only [newId] and expect the existing entries to survive; that is the pattern the samples avoid, and Workday does not document a merge. To remove an entry, read the list, take the id out and PATCH the remainder. The Work From Almost Anywhere sample's deleteAttachments page does this with removeAll on the ids loaded in onLoad.
Workday does not document this, but it follows from the pattern: two people updating the same record at the same moment can overwrite each other's additions, because each page sends the list it read at load time. If concurrent additions are normal for your process, use a joiner object instead (below).
Linking files uploaded on the same page
The same pattern links attachments. In the Work From Almost Anywhere sample, a request business object has a MULTI_INSTANCE field, rightToWorkVerification, targeting an attachment object. On the create page the outbound endpoints run in the order they are listed (Concept: Submitting Data): the request is POSTed, then the files go to the attachment's Collection endpoint (eventAttachments), then a PATCH on the request sets the field. Its onSend turns the Collection response into the field value:
let newList = [];
eventAttachments.rightToWorkVerifications.forEach(file => { newList.add({'id':file.id}) });
let payload = {'rightToWorkVerification': newList};
return payload;
The PATCH is excluded when the uploader is empty (empty(attachmentFileUploader.children)), so a submit without files leaves the field alone. The edit page in the same sample starts from request.rightToWorkVerification ?? [] and appends, as in the work-event pattern. The upload side is covered in attachment objects: POST, multipart and updates.
Letting users pick several values on a page
On an edit page, an instanceList with multiSelect set to true lets a user choose several instances, and selectedValues pre-selects the ids already stored so an edit page opens showing the current list (instanceList). Pre-selecting the stored values is what makes the page safe: the user edits the full current list, so the list you submit is the full new list the PATCH needs. Getting the pre-selection right is the subject of instanceList selectedValues.
Limits that shape the design
These come from Reference: Model Component Attributes and Reference: Limits on Model Components:
- A business object can have 10
SINGLE_INSTANCEandMULTI_INSTANCEfields combined, and an app can have 25 of them that target Workday-delivered objects. - A
MULTI_INSTANCEfield cannot be indexed. Extend generates query parameters only for indexed fields, so there is none for it, and it is not available as an indexed filter in REST queries, custom reports or the WQLWHEREclause. - It cannot use
secureByTargetoruseForDisplay. - An existing
SINGLE_INSTANCEfield cannot be converted toMULTI_INSTANCE. Changing cardinality means adding a new field and moving the data. - Workday recommends keeping each field under 1,000 instances; more can affect performance.
- Through the Graph API, reading a multi-instance field as a related object returns at most 500 related instances, and the request errors if there are more (Reference: Limits on Graph API).
When to use a joiner object instead
A MULTI_INSTANCE field suits a short list that one person edits at a time on one record: reviewers, related projects, attached evidence. Workday's guidance for a many-to-many relationship is a joiner business object, with one instance per link and a SINGLE_INSTANCE field to each side (Concept: Joiner Business Objects). The Create a Work Event sample does both: each Registrant is a joiner instance pointing at a worker and an event, and the event also keeps a registrants list for display.
The joiner side is easier to query because its SINGLE_INSTANCE fields are indexed automatically, which is why the sample checks "already registered" with WQL against the joiner, not the list:
SELECT worker FROM {appId}_registrants WHERE event = {eventWid} AND worker = {workerWid}
Adding a joiner row is a POST, so two people registering at once cannot overwrite each other. To delete many joiner rows in one request, use ?bulk=true, which handles up to 100 instances per request (Reference: Business Object API Requests). Needing its own attributes, per-entry filtering or security, or concurrent writes are my reasons to prefer a joiner; they are design judgement, not documented thresholds.
Reading the result back
Prove the update with a GET on the record, not with the page closing cleanly:
curl "https://api.workday.com/apps/{appId}/v1/equipmentRequests/{requestId}" \
-H "Authorization: Bearer {token}"
Check that reviewers holds every id you expected, including the ones that were there before the change, and nothing you removed. In WQL, select the field with nested fields, as the sample does with registrants { worker }, to see the referenced values. Run the check once from a record that started empty and once from a record that already had entries; the second case is where a missing read-then-append shows up.
Purging a MULTI_INSTANCE field removes the relationships, not the referenced instances (Steps: Purge Data from Extend Objects), so a purged list reads back empty while the workers or files it pointed at still exist.