The widget that shows a file is attachmentList

A file appears on an Extend page through attachmentList, either on its own or as the attachment of a fileUploaderRow inside a fileUploader. Its attachmentURL must be a URL that returns the file's bytes, attachmentAuth must match what that URL needs (sso for Workday APIs), and attachmentUriTemplate must be [Target] or the name isn't clickable.

Workday states that attachmentList currently previews only PDF and Microsoft Word documents. Any other type, images and spreadsheets included, downloads when the user clicks it. The item's mimeType decides whether the file can preview, so a PDF that downloads instead of opening needs application/pdf there.

For one file, use the widget on its own. instanceList holds exactly one item describing the file:

{
  "type": "attachmentList",
  "id": "policyDocument",
  "label": "Equipment policy",
  "attachmentURL": "<% 'https://api.workday.com/apps/' + site.applicationId + '/v1/policyFiles/' + policy.file.id %>",
  "attachmentAuth": "sso",
  "attachmentName": "<% policy.file.fileName %>",
  "attachmentUriTemplate": "[Target]",
  "instanceList": [
    {
      "action": "attachment",
      "descriptor": "Read the equipment policy",
      "mimeType": "application/pdf",
      "iconId": "mime_pdf"
    }
  ]
}

attachmentName is the name the file is saved under when downloaded. For an Extend attachment object, Workday says to use the object's fileName here, not its descriptor. iconId takes one of the documented values, such as mime_pdf, mime_msword, mime_msexcel or mime_image.

Which URL returns the file

A link that opens the record's JSON instead of the file is pointing at the wrong URL or sending the wrong header.

Your app's attachment objects. Calling GET on an attachment object instance without an Accept header returns the file data. With Accept: application/json it returns only the fields. A browser link sends no such header, so the instance URL works as attachmentURL. The Charitable Donations sample app builds it as https://api.workday.com/apps/<% site.applicationId %>/v1/charityFiles/<% file.id %>.

Graph API results. Query the attachment and ask for downloadID, descriptor and contentType. Then point the link at the Attachments REST API, https://api.workday.com/attachments/v1/graphql/<% file.downloadID %>. A downloadID expires after one hour, so get it in the page's inbound endpoint when the page loads. Don't store it.

Workday REST attachment resources. Some delivered services expose attachments on a resource, and in several the same GET returns either metadata or the binary depending on a type query parameter. Procurement's requisition attachments use ?type=getFileContent, for example. Read the service's API reference for the exact parameter before building the URL.

Public files. A file on a public web server can use its plain URL with attachmentAuth set to NOAUTH, which is the default.

Listing every file on a record

To show several attachments, put a loop inside a fileUploader and emit one fileUploaderRow per file. Set enabled to false on a view-only list. This follows the pattern in Workday's Charitable Donations sample:

{
  "type": "fileUploader",
  "id": "requestFiles",
  "label": "Supporting documents",
  "enabled": false,
  "children": [
    {
      "type": "loop",
      "on": "<% requestFilesEP.data %>",
      "as": "file",
      "children": [
        {
          "type": "fileUploaderRow",
          "render": "<% !(empty file.fileName) %>",
          "fileName": "<% file.fileName %>",
          "fileType": "<% fileType:getFileType(file.fileName) %>",
          "attachment": {
            "type": "attachmentList",
            "attachmentURL": "<% 'https://api.workday.com/apps/' + site.applicationId + '/v1/requestFiles/' + file.id %>",
            "attachmentAuth": "sso",
            "attachmentName": "<% file.fileName %>",
            "attachmentUriTemplate": "[Target]",
            "instanceList": [
              {
                "action": "attachment",
                "descriptor": "<% file.fileName %>",
                "mimeType": "<% fileType:getFileType(file.fileName) %>"
              }
            ]
          }
        }
      ]
    }
  ]
}

Three documented details matter here:

  • Inside a fileUploader, only the uploader's label displays, and the item's descriptor and target are ignored. The row's fileName is what the user sees.
  • fileType:getFileType works out a MIME type from the file name, but only for doc, docx, htm, html, pdf, rtf and txt. For a .jpg or .xlsx it returns null and the row falls back to the default icon. Attachment objects and Graph queries return a contentType for every file. Workday's Charitable Donations sample maps it with a small script module, fileTypes.script (fileTypes.getMimeTypeIDbyContentType(file.contentType.id)), and the Graph download example reuses that module.
  • The render guard skips empty rows. Without it, a record with no file still renders a row with nothing to open.

If the same list must also accept new uploads, leave the uploader enabled and give it a valueOutBinding. The Extend file uploader covers the submit side.

An image should usually display on the page rather than download. Use the image tag with its endPoint attribute pointing at the attachment object instance, as Workday's example does with "url": "<% 'charityImages/' + charity.image.id %>", "baseUrlType": "app" and "authType": "sso". Guard it with render so a record without an image doesn't show a broken frame.

Don't paste a Base64 image string into imageUri. The image documentation warns that the serialized PMD is limited to 100K on the server, and a page that exceeds it stops working.

Worker documents are a different problem

Documents attached to a worker in Workday (the worker's documents by document category) aren't app attachment objects, and the REST API reference has no resource that returns them to a page. They're available through the Staffing web service's Get_Worker_Documents operation, which returns each document's File as Base64 content together with its category, comment and worker. Pages call REST endpoints, so a page can't fetch that content and link to it directly.

There is no documented direct route here, so this is a design choice. An orchestration reads the document and the page shows it from somewhere the page can link to, usually a copy in an app attachment object. Copying a worker document duplicates personal data into your app, so decide who can see the copy and when it's removed before you build it. The full pattern, including the worker profile tab, is in worker documents on the worker profile.

Workday documents another path for a file the app creates itself, such as a generated letter: produce the PDF in an orchestration and store it as an attachment. See generating a PDF attachment in Orchestrate.

How to check that the right person sees the right file

With sso, the browser requests the file as the signed-in user. Workday describes the Attachments REST API as returning attachments the user has access to, and an attachment object instance is secured like the rest of your app. Test it that way:

  1. Open the page as a user who should see the file. Click each link. The PDF should preview, other types should download, and the saved file name should match attachmentName.
  2. Open the same page as a user who shouldn't see the file. The link should fail for them. If it doesn't, the security domain on the attachment object is wider than you meant.
  3. For Graph links, reopen a page that has been open longer than an hour and click again. If the link has expired, reload the page to fetch fresh downloadIDs.

If a link opens JSON or an error page instead of the file, request the same attachmentURL yourself without an Accept header and compare. The response tells you whether the URL is wrong or the page is passing the wrong ID.

Sources checked