Orchestrate does not run XSLT

There is no XSLT step in Orchestrate, and no expression function that applies a stylesheet, so an XSLT transformation has to be rebuilt rather than copied: you take what the stylesheet does and express each part with Orchestrate's components and XML functions. Workday's Getting Started with Orchestrate positions orchestration templates as the way to avoid hand-written XSLT, which tells you the product was designed for this rebuild rather than for hosting stylesheets.

Most integration stylesheets use a small set of constructs: iterate over records, write an output structure, pick values with XPath, branch, group, sort, look up a value in another document, and occasionally copy the input with a few changes. Each has a direct equivalent.

Read the stylesheet as a list of jobs first

Before opening Orchestration Builder, write down what the stylesheet produces and from what. For a typical worker extract that means the input root and repeating element (for example wd:Report_Entry), the output format (XML, CSV, JSON or fixed text), each output field and the XPath or logic behind it, any grouping, sorting or totals, and any lookup to a second document or a hard-coded value table.

That list becomes the orchestration. It also tells you which stylesheet logic is incidental: named templates that only exist to work around XSLT 1.0, or xsl:choose blocks that format a date, usually collapse into one expression.

XSLT constructs and their Orchestrate equivalents

XSLT Orchestrate
XSLTxsl:for-each select="..."OrchestrateA Loop whose Data Set is data.Step.xml.iterator("..."), or {{#each ...}} inside a Create Text Template
XSLTLiteral output tree with xsl:value-ofOrchestrateCreate Text Template set to application/xml, text/csv, application/json or text/plain, with {{ }} expressions
XSLTBuilding output across iterationsOrchestrateAn aggregation on the loop: XML Fold, CSV Fold, JSON Fold or Text Fold, each with header and footer where offered
XSLTxsl:if, xsl:chooseOrchestrate{{#if}} in a template, a Branch on Conditions step, or an inline (if (cond) a else b) expression
XSLTxsl:sortOrchestrateThe Loop's Sort By: text sorts lexicographically, numbers numerically
XSLTxsl:for-each-group, Muenchian groupingOrchestrateBatch Loop with the Group strategy and a Group By expression
XSLTDistinct valuesOrchestratedistinct(xpath, keyXPath, header, footer, separator, includeXmlDeclaration) on the XML value
XSLTxsl:key with key(), or document() lookupsOrchestrateA Join Loop that matches the two datasets on a common key
XSLTsum(), count()OrchestratenumbersAtXPath("...").sum(), countItemsAtXPath("..."), or a Sum aggregation on the loop
XSLTxsl:call-templateOrchestrateA named template ({{#template name(arg: Type)}}) or a user-defined function ({{#function}}) inside the text template
XSLTIdentity transform with small changesOrchestrateremoveFragments, replaceFragments, removeAttributes, replaceAttributes, replaceNamespace, excludeAllNamespaces
XSLTXML to JSON outputOrchestrateconvertToJson(ignoreAttributes, tryConvertTypes)

The XML functions are in the Xml member functions reference (sum() is a NumberList function), the template constructs in Concept: Text Templates, and the fold strategies in Add Aggregations.

A worked example: a worker XML to a CSV file

A stylesheet that writes one CSV line per worker:

<xsl:template match="/">
  <xsl:text>Employee_ID,Name,Cost_Center&#10;</xsl:text>
  <xsl:for-each select="wd:Report_Data/wd:Report_Entry">
    <xsl:sort select="wd:Employee_ID"/>
    <xsl:value-of select="concat(wd:Employee_ID, ',', wd:Name, ',', wd:Cost_Center, '&#10;')"/>
  </xsl:for-each>
</xsl:template>

In Orchestrate, put the whole thing in one Create Text Template with the content type text/csv, reading the response of an earlier Send Workday RaaS Request step called GetWorkers (requested as XML):

Employee_ID,Name,Cost_Center
{{#each data.GetWorkers.response.asXML().excludeAllNamespaces().iterator("/Report_Data/Report_Entry") as row}}
{{item.row.stringAtXPath("/Report_Entry/Employee_ID")}},\
{{item.row.stringAtXPath("/Report_Entry/Name")}},\
{{item.row.stringAtXPath("/Report_Entry/Cost_Center")}}
{{/each}}

The backslash at the end of a line is the template's line continuation: the backslash and the line break are dropped, so each worker comes out on one line. excludeAllNamespaces() removes the wd: namespace so the XPath can use plain names.

Two things the stylesheet did implicitly now need a decision. Text templates have no sort construct, so the xsl:sort has nowhere to go inside {{#each}}. If order matters, use a Loop with Sort By on Employee_ID and a CSV Fold aggregation instead of the inline #each. And xsl:value-of wrote names containing commas straight into the file. Quote those fields, or build the file through a CSV Fold with a CSV format from your app's Settings so the quoting follows the format. The Create Text Template guide covers template escaping, and XML to CSV in Orchestrate covers the CSV side in full.

Where XPath in Orchestrate behaves differently

Orchestrate's XML functions take an XPath string, and the ones you will use most are stringAtXPath, numberAtXPath, stringsAtXPath, iterator and exists. A few behaviours catch stylesheets in translation.

Missing values fail the step. stringAtXPath throws an exception if the path does not point to a value. An XSLT xsl:value-of on a missing node outputs nothing. For optional fields, use stringAtXPathOrEmptyString or stringAtXPathWithDefault to get the XSLT behaviour.

Iterator items are rooted at the element. Inside a loop over /Report_Data/Report_Entry, an item's paths start at /Report_Entry/.... Workday's Orchestrate Sampler shows the same pattern: a Batch Loop over //Worker reads each item with stringAtXPath("/Worker/emailID"). A relative path copied from the stylesheet's select will not match.

Some predicates need a function instead. The Xml reference describes filterWhereElementAbsent as a workaround for XPath predicates such as not(element) or count(element)=0. If the stylesheet filters records on a missing child, use that function rather than porting the predicate.

Escaping is explicit. Text from stringAtXPath is the plain value. xmlString and xmlStrings return values already escaped for inclusion in XML, which is what you want when the output is XML built in a text template. The special characters in SOAP calls guide shows where this goes wrong.

Grouping and lookups

Grouping is where stylesheets get long and Orchestrate gets short. A Batch Loop with Strategy Group and a Group By of data.BatchLoop.item.stringAtXPath("/Report_Entry/Cost_Center") gives you one pass per cost center, with the group's records in data.BatchLoop.batch and the group value under the Group Name you set. Totals per group are one expression, for example data.BatchLoop.batch.numbersAtXPath("//Amount").sum() as in the Sampler's Batch Loop group example. Sort By in a Batch Loop sorts inside each batch, not across the dataset. See the Batch Loop guide.

A stylesheet that loads a second document and matches on a key (key() or document()) becomes a Join Loop: the main document is the Primary Dataset, the lookup document is a Joining Dataset with its own Primary Dataset Key and Joining Dataset Key, and the loop body sees each primary record with its match. The Join Loop reference caps a joining dataset at 500 MB. The Join Loop guide covers keys and unmatched records.

When to keep the XSLT

Rebuilding is not always the right call. An existing Workday Studio integration whose stylesheet streams a very large document, or a Document Transformation that already works and has no other reason to change, can stay where it is. The Orchestrate vs Studio comparison and the Studio XSLT streaming guide cover that decision. Rebuild when the integration is being reworked anyway, or when the stylesheet is the part nobody on the team can maintain.

Prove the rebuild matches the old output

Run the old transformation and the new orchestration on the same input file and compare the outputs byte for byte, then field by field where whitespace or attribute order differ. In the orchestration, write the result with a Store Document step attached to the integration event so you can download exactly what it produced.

Pick the input to exercise the stylesheet's edge cases: a record with an optional field missing, a value with &, < or a comma, a non-ASCII name, the first and last records of a group, and an empty input. A difference on any of those is a translation decision you have not made yet.

Sources checked