What the limit is
Workday runs every PMD script execution under fixed limits, and a script that crosses one throws an error instead of finishing. The limits, from Reference: Limits on Presentation Components:
| Limit | Value |
|---|---|
| LimitCPU time per script execution | Value5 seconds |
| LimitMemory per script execution | Valueallocated by Workday; no figure published |
| LimitCall frames (stack depth, such as recursion) | Value25 |
| LimitNesting of script modules | Value2 levels |
| LimitScript size | Valuewithin the 100 KB limit on a PMD, pod or .script file |
Two details narrow the cause. The 5-second CPU limit doesn't include endpoint invocations or widget API calls, which run in separate threads. So a slow invoke() doesn't use up script CPU time; it runs into the endpoint limits instead (24 seconds per endpoint, 60 seconds per request). And the memory threshold isn't a number you can design against, so the documented fix is to use less: Workday's best-practice page lists the habits that do.
Going past two levels of script-module nesting is a validation error, so it never reaches a user. A run-time script failure on a page that validated cleanly therefore comes from CPU time, memory, stack depth or an ordinary script error such as a null reference (covered below).
Why scripts run out: the data was loaded to be filtered on the page
The typical heavy script pulls a large collection from an endpoint and then filters, joins or reshapes it with loops in PMD. The loop is small; the data is not. Workday's memory-management guidance in Concept: PMD Best Practices names each form of it:
- Nested loops use more memory than you'd expect, for example a loop over workers inside a loop over requests.
filterused to find one match walks the whole list.find(closure)stops at the first match.- Repeatedly indexing the same element (
list[i].a + list[i].b + …) inside a loop costs extra memory. Workday's example halves memory by iterating withfor (var obj : list)and readingobj.a,obj.b. - One very large function holds everything it touches at once. Splitting it into smaller functions invoked at different points on the page lowers peak use.
The same page adds the rule that removes the problem at its source: if the endpoint or report can filter the data with query parameters, filter it there, not in script.
Find the expensive script in the logs
Don't guess which handler is heavy. Workday logs CPU and memory use for every script execution.
- On the Workday Developer Site, open the Logs tab of the Analytics dashboard (from the Console section), or the same logs from the app's home page.
- If the page showed an error with an ID such as
VPS|5a43…, filter onwd_common_request_id isthat ID to get only that request's entries. - Add
wd_category is scriptUsage. These INFO entries carry the CPU and memory use of each script execution, and Workday says the app log shows the function invocation with the highest usage on the page. - Add or swap in
wd_category is pageError.scriptto read the exception itself. In a Development tenant the page error also includes the script error details.
Note which attribute or handler the top entry belongs to (a values binding, an onLoad, a cell template, an onChange) before changing anything.
Fixes, by where the heavy script lives
In onLoad or a values binding over a large endpoint
Push the filter into the request. For a Workday REST endpoint, use the resource's query parameters. For WQL, put the condition in WHERE and select only the fields the page uses (the WQL cheat sheet has the clause syntax). If the page needs a lookup table, build it once in onLoad into pageVariables rather than recomputing it in every binding. onLoad runs once, after the inbound endpoints return and before any widget loads.
Joining two collections
Replace the inner loop with a map keyed on the join field. Workday's best-practice example builds the map in one pass and then reads it with map.get(key):
var byId = {:};
for (var w : workers.data) {
byId.put(w.id, w);
}
requests.data.map(r => {
var w = byId.get(r.requester.id);
{ 'id': r.id, 'descriptor': r.descriptor + ' - ' + (!empty w ? w.descriptor : '') }
});
That turns "for every request, scan every worker" into one pass over each list.
Grids and lists with many rows
A cell template is evaluated once per row, so a lookup that's cheap once becomes expensive across a full page of rows. Compute per-row values before the grid renders (in onLoad or in the script that shapes rows) and bind cells to simple fields. If the grid simply has too many rows to process at once, page it from the endpoint rather than loading everything. See paginated grids in Extend.
Recursion
A recursive function that walks an org hierarchy or a nested structure is capped at 25 call frames. Rewrite it as a loop over a work list, or fetch the hierarchy level by level with deferred endpoints as the user drills down.
One huge script module
Split it so each page includes only what it uses, keeping to two levels of nesting. Workday's Concept: Script Modules page asks that exported properties be stateless: static variables and function declarations only.
Things that look like the script limit but aren't
A page that times out while waiting for endpoints is hitting the 24-second endpoint or 60-second request timeout, not the script CPU limit, because endpoint calls don't count toward it. Fewer or faster endpoints fix that, not script changes.
A null reference also fails the script. Workday's best practices say to check empty before indexing into a list from an endpoint, and to default flowVariables properties with ?? or ?:. Its exception appears under pageError.script like any other script failure, so read the message rather than assuming a limit. Workday Extend page errors covers the other common page failures.
Confirming the fix with scriptUsage
Run the page with the same data that failed (the same worker, filter or record), because these failures depend on volume. Then compare the scriptUsage entries for that request before and after the change. The top function's CPU and memory figures should drop, and no pageError.script entry should follow. Test at the largest data volume the page will see in production, not in an empty sandbox. A script that passes on 30 rows and fails on 3,000 hasn't been fixed.