How Orchestrate zips and unzips files
Orchestrate unzips with functions on the Data type and zips with an aggregation strategy on a loop; there is no dedicated "Zip" or "Unzip" step. To read an archive, call listFilesInZipArchive() and fetchFileFromZipArchive(path) on the zip's Data value. To build one, loop over the files and add a Zip Archive aggregation, which collects each iteration's data at a path you choose.
The Data functions involved, from Workday's Data Member Functions reference:
| Function | Returns |
|---|---|
FunctionlistFilesInZipArchive() | ReturnsStringList of the paths of every file in the archive |
FunctionisFileInZipArchive(filePath) | Returnstrue if that path is in the archive |
FunctionfetchFileFromZipArchive(filePath) | Returnsthe file at that path, as Data |
Functionunzip() | Returnsa DataIterator over every file in the archive |
Functiongzip() | Returnsthe data compressed as gzip |
Functiongunzip(contentType) | Returnsdecompressed gzip data, re-encoded to UTF-8, typed by contentType |
Get the zip into the orchestration as Data
Every function above works on a Data value, so the first step is getting the archive into one.
- A file attached to the integration launch is an event document. Read it with the
documentsglobal functions, for exampledocuments.name("payroll-export.zip").get()ordocuments.labelsMatchAny(List.stringList("Deliverable")).get(). - A file returned by an HTTP call is the response of the Send HTTP Request or Send Workday API Request step.
- A zip that arrives as base64 inside JSON or XML has to be decoded first (more on that in base64 attachments in Orchestrate). Workday's UnzipOneFile sample reads the string and decodes it in one expression:
data.ZIP_Sources.message.stringAtXPath("/zip/data").base64decode("application/zip")
If the archive is also PGP-encrypted, decrypt before unzipping: pgpDecrypt(resource.pgpCredential("<credential id>"), true) on the encrypted data returns the plain archive, and the zip functions apply to that result. The private key is set up as a PGP credential (see Configure PGP Encryption).
Unzip one known file, or every file, with its name
When you know the file you want, check for it and fetch it:
data.zip.Data.isFileInZipArchive("workers.csv")
data.zip.Data.fetchFileFromZipArchive("workers.csv").asCSV()
When you do not, list first. The UnzipOneFile sample is a good shape to copy: it builds the list, counts it, stops with a clear message if the count is wrong, and stores the file under its own name.
- Create Values:
FileList=data.ZIP.Data.listFilesInZipArchive(),ListSize=data.ZIP.FileList.size(). - Continue on Conditions: assert
data.ZIP.ListSize.equals(1), with a message that includesdata.ZIP.FileList.mkString(","). If the assertion is false, the orchestration stops and logs that message, so a partner who sends two files gets a readable failure instead of a wrong load. - Store Document: document
data.ZIP.Data.fetchFileFromZipArchive(data.ZIP.FileList.get(0)), titledata.ZIP.FileList.get(0).
For archives with many files, loop over listFilesInZipArchive() rather than over unzip(). Both reach every file, but iterating the list gives you each file's path as the loop item, which you need for the stored document's title, for routing .csv and .pdf files differently with endsWith, and for the error message when one file fails. Use unzip() when the name genuinely does not matter, for example when every file has the same layout and is processed the same way.
The list holds each file's path inside the archive, which can include folder names. Use substringAfterLast("/") when you need the bare file name for a document title.
Build a zip with the Zip Archive aggregation
On a Loop, Batch Loop or Join Loop, open the three-dot menu, choose Add Aggregation, add an output and select the Zip Archive strategy. It has two fields:
- Select Data: the
Datafor the current iteration. - Path: the file's path inside the archive, as an expression.
The loop's output is the archive, which you pass to Store Document or send on. The Employee Relations Incident Management sample zips case attachments this way. Each iteration retrieves one attachment inside a Branch on Conditions step, the aggregation takes that branch's response as the data, and the path is built as:
s"${data.Loop_Attachment_ZIP.itemNumber}_${data.FileInfo.fileName}"
The item-number prefix is the useful detail. Attachments from different records often share a name such as receipt.pdf, and making each path unique avoids any question of what the archive does with two entries at the same path.
Like every aggregation, Zip Archive takes a Condition, and only iterations where it is true are added. If the download step has an error handler set to Conditional Outputs, use its isSuccess output as the condition so a failed download is left out of the archive instead of stopping the loop. Select Fail When No Inputs if an empty archive should stop the run rather than be sent.
gzip is not zip
gzip() and gunzip(contentType) compress and decompress a single stream; they do not read or write zip archives. Use them when an API sends Content-Encoding: gzip or a partner asks for a .gz file. gunzip needs the content type of the original data ("text/csv", "application/json") and returns a value of that type, already re-encoded to UTF-8. A .tar.gz holds several files inside one gzip stream, and none of the documented functions read tar, so after gunzip you would have the tar bytes, not the files.
None of the zip functions takes a password argument, so a password-protected zip from a partner needs a different agreement, such as PGP encryption of a normal zip, which Orchestrate supports.
Size limits that apply to archives
Workday's Orchestration Runtime Limits page sets 200 MB of concurrent data in memory and 2 GB on disk. Workday keeps large values in file-backed structures on disk rather than in memory, which is why a large archive can be in scope without exhausting the memory limit, and disk space is released when a value goes out of scope. An individual HTTP response can be up to 500 MB, and the message that launches an orchestration up to 20 MB.
Workday's other runtime limits, and how to design around them, are covered in keeping large loads inside Orchestrate's limits. In practice: fetch and process one file per loop iteration rather than collecting every extracted file into a list first, so each file goes out of scope when its iteration ends.
Prove the archive is right
For unzipping, write the file count and the list of names (FileList.mkString(",")) into an Add Integration Message, and store or log each extracted file's size with contentLength(). Compare that with the partner's manifest or with the archive opened on your own computer.
For zipping, download the stored archive from the integration event and open it. In an Integration System orchestration, Store Document only attaches it to the event when Attach document to integration event is set to true. Check the number of entries against the loop's item count (a Count aggregation on the same loop gives you that number), and open one file from each source type to confirm it is the document, not an error response that was zipped in its place.
Sources checked
- Reference: Data Member Functions
- Add Aggregations
- Reference: Documents Global Functions
- Reference: Continue on Conditions
- Reference: Store Document
- Reference: Orchestration Runtime Limits
- Handle Errors
- Configure PGP Encryption
- App Catalog sample: Orchestrate for Integrations Sampler (UnzipOneFile)
- App Catalog sample: Employee Relations Incident Management