bpExtender is a page container, not a process extension
bpExtender adds business process furniture to an Extend edit page: a comment box, a comment history list, a process history list, an attachment area and your own buttons, all in Workday's standard business process look. It does not attach anything to a Workday-delivered business process or change how one routes. Every button it shows only does what the outbound endpoints you wire to it do.
If what you want is a step of your own inside a delivered process, such as Hire or Onboarding, the mechanism is an Extend subprocess: an Extend business process added under a parent delivered process from the Parent Business Processes tab in App Builder. The Onboarding and Hire guide walks through it. bpExtender may then appear on that subprocess's pages, but it's the subprocess that does the extending.
Where bpExtender can go
Use bpExtender only on edit pages. It sits in the extender attribute of presentation, at the same level as body, and renders at the bottom of the page. Its child bpButton and bpTemplatedList tags have the same edit-page-only rule. Workday lists Android and iOS support for all three.
On an Extend business process, that rules out the approval page in practice. Workday recommends a view page for approval steps; if you use an edit page there, the framework replaces the Submit button with its own Approve, Deny and Send Back buttons and the page calls none of its outbound endpoints. So your bpButtons and their endpoints wouldn't run. Initiation, action and revise pages are the business process pages that can call outbound endpoints, which makes them the useful homes for bpExtender, along with any standalone edit page in the app.
The parts and what each needs
Workday's tag reference marks all of these required:
comment: atextAreawhosevalueOutBindingpoints at the outbound field that receives the comment, such asapproveEndpoint.comment. Its label is always "Enter your comment" and can't be changed. To hide it, usevisible, notrender.commentHistory: abpTemplatedListwith acommentHistorytemplate (author, date, text).processHistory: abpTemplatedListwith aprocessHistorytemplate (actionEvent, plus optional workflowStep, status, dates, user and delegationMessage).attachment: afileUploaderwhosevalueOutBindingnames the outbound endpoint that stores the file. The file uploader guide covers that widget.image: the picture next to the comment. It defaults to a cloud image; Workday's example uses the signed-in worker's photo fromworkers/me?view=workerPhotoWIDViewwith"userPhoto": truefor a round image.bpButtons: the buttons that replace the page's standard OK button. If you leave them out, OK shows. Cancel always shows.
Filling the histories from the Business Process REST service
The two lists don't fetch anything themselves. Point each on attribute at an inbound endpoint, and the template maps fields from each row. Workday's examples read the event's comments and completed steps:
{
"name": "commentHistoryApi",
"baseUrlType": "workday-businessProcess",
"url": "<% 'events/' + queryParams.eventId + '/comments' %>",
"authType": "sso"
},
{
"name": "processHistoryApi",
"baseUrlType": "workday-businessProcess",
"url": "<% 'events/' + queryParams.eventId + '/completedSteps' %>",
"authType": "sso"
}
with workday-businessProcess defined in the AMD's dataProviders as https://api.workday.com/businessProcess/v1/. In the templates, a comment row's fields are person.descriptor, comment and commentDate; a completed step's are descriptor, status.descriptor, completedDate and completedByPerson.descriptor.
Two details matter here. Workday REST dates come back in UTC, so set displayTimeZone on each date tag; Workday's example reads the user's zone from the Common service's userInfo endpoint. And queryParams.eventId exists only on pages routed with {eventId}. Action and revise pages are routed with {eventStepId}, so on those pages call GET eventSteps/{eventStepId} first and build the history URLs from its event.id.
Making the buttons do something
Presentation Components submits every outbound endpoint on the page. bpButtons don't change that; they only let you tell which one was clicked. Workday's pattern has four parts.
Give each button a unique id starting with a letter. When clicked, bpExtender sets that button's value to its id, so only the clicked button has a non-empty value:
"bpButtons": [
{ "id": "approveButtonId", "type": "bpButton", "buttonType": "PRIMARY", "label": "Approve" },
{ "id": "denyButtonId", "type": "bpButton", "buttonType": "AUXILIARY", "label": "Deny" }
]
Put an exclude on every endpoint that belongs to one button, so the others are skipped:
{
"name": "approveEndpoint",
"url": "/approvals/post",
"baseUrlType": "myApiHost",
"authType": "sso",
"allowEmptyPayload": true,
"exclude": "<% empty approveButtonId.value %>"
}
An endpoint without exclude, such as the attachment upload, runs on every click; if an approve and a deny call both reach the server from one click, a missing exclude is the cause to look for.
Copy each button's value into a flow variable with an outboundVariable of "variableScope": "flow", and branch on it in the AMD flow definition: a transition with "condition": "<% !(empty(flowVariables.approveButton)) %>" goes to the confirmation for approval, and a true fallback goes to the other.
buttonType takes PRIMARY, AUXILIARY (the default) or LINK. LINK isn't supported in the Workday mobile app, so avoid it on pages people open from their phones.
Which endpoints the buttons should call
bpExtender works with Workday's business process REST APIs or a third party's. On an action or revise page of an Extend business process, the documented way to advance the step is POST eventSteps/{eventStepId}/submit in the businessProcess service, with stepAction.id set to d9e4223e446c11de98360015c5e6daf6 (Submitted) or, on action steps, d9e41a8c446c11de98360015c5e6daf6 (Saved for Later). Two buttons, Submit and Save for Later, each bound to one of those calls through exclude, is a pattern that fits bpExtender well. The submit endpoint's request body takes an optional comment, which is where the bpExtender comment box can bind.
Save the business object first, then submit the step. Outbound endpoints run in the order they appear in the PMD, so place the record update above the submit.
Prove it on the page
Run the page from its real entry point, not App Preview alone, because the histories need a live event:
- Open the step from My Tasks as the assigned person and confirm both history lists show the event's existing comments and steps with local times.
- Click each button in turn on a fresh event. In the application logs on the Developer Site, whose entries record each request's path in
wd_path, confirm only the intended outbound request ran for each click. - Read the result back:
GET events/{eventId}/commentsshould include the new comment, andGET events/{eventId}/inProgressStepsshould show the step moved on. For a saved record, read the business object instance and compare fields.