How a grid submits its rows

On an edit page, a grid submits rows through the outbound endpoints its cells bind to. By default it calls the endpoint once for each row, so a five-row grid makes five requests. Set the grid's isArrayOutBinding to true and it sends all rows together, as an array, in one request.

Which mode you need depends on what the receiving API expects. An API whose body holds a list of lines, such as a requisition with requisitionLines[], needs the array mode. An API that creates one record per call works with the default mode, but you then get one request, and one possible failure, per row.

Send all rows in one request

Three settings make the grid send one array:

  1. Set "isArrayOutBinding": "<% true %>" on the grid.
  2. Bind each cell's valueOutBinding to the array field with []: someEndpoint.someArray[].someField.
  3. Define the outbound endpoint once, with no per-row logic.

Workday's example sends requisition lines to the Procurement service:

{
  "type": "grid",
  "id": "lineGrid",
  "rows": "<% [1, 2] %>",
  "isArrayOutBinding": "<% true %>",
  "columns": [
    {
      "type": "column",
      "columnId": "quantityColumn",
      "label": "Quantity",
      "cellTemplate": {
        "type": "text",
        "valueOutBinding": "submitRequisition.requisitionLines[].quantity"
      }
    },
    {
      "type": "column",
      "columnId": "itemColumn",
      "label": "Item",
      "cellTemplate": {
        "type": "text",
        "value": "<% pageVariables.defaultItemId %>",
        "valueOutBinding": "submitRequisition.requisitionLines[].item.id"
      }
    }
  ]
}

With two rows, the body is a single requisitionLines array with two objects. With isArrayOutBinding false, the same grid sends two requests, each with a one-item requisitionLines array.

Workday's PMD best practices recommend this mode: submit all grid rows in a single outbound request rather than one request per row. The same page gives the reason: if one of several outbound requests fails, the earlier ones aren't rolled back, so per-row posting can leave half of a submission saved.

Bulk-post rows to an Extend business object

An Extend business object's API takes up to 100 instances in one request when you add ?bulk=true. POST, PUT, PATCH and DELETE all support it, and the body is a data array:

POST /apps/{appReferenceId}/v1/equipmentRequestLines?bulk=true
{
  "data": [
    { "request": { "id": "{requestId}" }, "item": "Laptop",  "quantity": 1 },
    { "request": { "id": "{requestId}" }, "item": "Monitor", "quantity": 2 }
  ]
}

From a grid, point the cells at the data array of a bulk endpoint and turn on array mode:

"outboundData": {
  "outboundEndPoints": [
    {
      "name": "postLines",
      "baseUrlType": "app",
      "url": "equipmentRequestLines?bulk=true",
      "httpMethod": "POST",
      "authType": "sso"
    }
  ]
}

Bind the cells with the same [] syntax, as postLines.data[].item and postLines.data[].quantity, and set the grid's isArrayOutBinding to true. Workday's own bulk example builds the body in an onSend handler instead, wrapping self.data in a data attribute with json:create(json:attribute('data', self.data)). Use that form if your bindings produce the rows but not the wrapper.

For bulk PUT and PATCH, each item must include the instance id. You can't use bulk DELETE from a PMD.

Read the per-row results

A bulk request doesn't succeed or fail as a whole. The response's data array holds one entry per item, in the same order as the request, each with its own status and body. A created row returns 201 with the new instance. A refused row returns 400 with an errors array that names the field and a code. Workday's example shows a refusal with code A5 when the user wasn't allowed to choose that worker in a constrained field.

The order rule matters because a 400 body carries the field and error code but not the row's position. Match entry 3 of the response to row 3 of what you sent.

New rows and changed rows in one grid

A grid that both adds and edits rows needs two operations. Workday's pattern for the default, per-row mode uses two outbound endpoints and the exclude attribute:

{ "name": "postLine",  "url": "equipmentRequestLines",
  "baseUrlType": "app", "httpMethod": "POST", "authType": "sso",
  "exclude": "<% ! empty lineId.value %>" },
{ "name": "patchLine", "url": "<% 'equipmentRequestLines/' + lineId.value %>",
  "baseUrlType": "app", "httpMethod": "PATCH", "authType": "sso",
  "exclude": "<% empty lineId.value %>" }

lineId is the id of a hidden or disabled cell holding the instance ID. An empty ID means a new row, which is posted. A filled ID means an existing row, which is patched. Every other cell uses valuesOut to bind the same value to both endpoints, postLine.item and patchLine.item, and the exclude logic picks which one runs for each row.

In array mode there's only one call per endpoint, so exclude can't split rows. Build the two arrays yourself in onSend instead. Its script can read every grid row, and returning null stops a POST, PUT or PATCH from being sent. So a bulk POST endpoint can send only the rows without an ID, and a bulk PATCH endpoint can send only the rows with one, each returning null when it has nothing to send.

Deleting rows is a separate path. Set the grid's deleteEndPoint to a DELETE outbound endpoint whose URL uses the row's ID cell.

More than 100 rows, or slow targets

A PMD request has 60 seconds for all of its endpoint calls, and each endpoint has 24 seconds. A large grid posting to a slow API runs into those limits before it runs into the 100-instance bulk cap. When a submission can exceed 100 rows, or each row needs several calls, send the rows to an orchestration once and let it do the writing. A Batch Loop with a size of 100 turns any number of rows into bulk-sized batches. See the Batch Loop guide.

Prove every row arrived

After submitting, read the records back rather than trusting the page's success message. Use GET /equipmentRequestLines?request={requestId}, the indexed SINGLE_INSTANCE field as a query parameter. The number of instances returned should equal the number of grid rows, and each line's values should match what was entered. Test once with a row you expect to be refused, and check that the page shows the error and the other rows behave as you designed. For checks that should stop a submission before anything is sent, see grid validation in Extend.

Sources checked