What Workday already records
Extend business objects support Workday's audit trail, the same as delivered objects. Workday's comparison of delivered and Extend business objects marks Audit Trail as supported for both, and the Extend developer course states that standard audit trail report capabilities work with Extend business objects.
What Extend business objects don't have is effective dating. A delivered object such as a worker's job can be read as of a date; an Extend record has one current state. The audit trail tells you that a field changed; it doesn't let you report "what did this request look like on 1 March" with an ordinary custom report.
Three needs tend to get lumped together as "audit trail", and each has its own answer:
- Who changed what, for compliance or troubleshooting: the standard audit trail.
- A visible Created By and Last Updated on the record, filterable in reports and on pages: fields you define and fill.
- The record's values at a point in time, or every past version: a separate change-log object.
Why business process history can show newer data
Workday doesn't lock an Extend business object while its business process is in progress, nor after approval. If a page or orchestration edits the record after approval, the event details in the business process history can display the updated values. Workday notes that the business object's audit trail still reflects the change.
If approvers must be able to see what they approved, don't build any path that edits an approved record in place. When post-approval changes are part of the process, Workday suggests an action step right after approval, an asynchronous orchestration, or a scheduled integration; pairing any of those with a change-log record keeps the approved values readable. The model business process guide covers the step options.
Adding Created By and Last Updated fields
Extend doesn't document system-maintained "created by" or "last updated" fields you can bind on a page or report, so the pattern in Workday's own sample apps is to define them on the object and set them on every write. Workday's Employee Relations Incident Management sample defines these on its case object:
{ "id": 4, "name": "createdDate", "label": "Date Created", "type": "DATE",
"precision": "MILLISECOND", "enableIndex": true },
{ "id": 5, "name": "createdBy", "label": "Created By",
"type": "SINGLE_INSTANCE", "target": "WORKER" },
{ "id": 10, "name": "lastUpdatedBy", "label": "Last Updated By",
"type": "SINGLE_INSTANCE", "target": "WORKER" },
{ "id": 11, "name": "lastUpdatedDate", "label": "Last Updated Date",
"type": "DATE", "precision": "MILLISECOND" },
{ "id": 12, "name": "lastUpdateType", "label": "Last Update Type", "type": "TEXT" }
A few design notes that follow from Workday's rules:
- Pick names outside Workday's reserved list.
id,descriptor,wid,workdayIDanddisplayIDare reserved, case-sensitive;entrymomentandeffectivemomentare reserved in any case. Names such ascreatedByandlastUpdatedDateare fine. SINGLE_INSTANCEfields are indexed automatically, socreatedByandlastUpdatedBycan be used as REST query parameters and efficient report filters. A business object can have up to 10SINGLE_INSTANCEandMULTI_INSTANCEfields in total and 50 fields overall, and an app can have 25 instance fields that target Workday-delivered objects such asWORKER, so these audit fields come out of those budgets.- Index the date you'll filter on with
"enableIndex": true, as the sample does withcreatedDate. You get up to 5 indexed regular fields per object. - Don't point audit fields at a
secureByTargetworker unless you mean to. Contextual security onlastUpdatedBywould make record access depend on who touched it last.
Stamping the fields on every write
The fields are only as good as the paths that set them. On a page, read the signed-in worker with the /workers/me endpoint, where me stands for the current user, and send their ID with the write (the current-user guide covers that endpoint). Condensed from the sample's edit-comment page:
"include": ["transform.script"],
"endPoints": [
{ "name": "currentUserData", "baseUrlType": "general",
"authType": "sso", "url": "common/v1/workers/me" }
],
"outboundData": {
"outboundEndPoints": [
{
"name": "putComment",
"baseUrlType": "app",
"url": "<% 'protectedCaseEntryBO/' + queryParams.commentID %>",
"httpMethod": "PUT",
"values": [
{ "outboundPath": "lastUpdatedDate", "value": "<% transform.getTimestamp(userTimeZone) %>" },
{ "outboundPath": "lastUpdatedBy.id", "value": "<% currentUserData.id %>" }
]
}
]
}
userTimeZone is a predefined app variable holding the signed-in user's preferred time zone. transform.getTimestamp isn't a Workday function: it lives in the sample's own transform.script module, and formats the current time with date:now().formatWithTimeZone('yyyy-MM-dd\'T\'HH:mm:ss.SSSZ', timeZone). Keeping that helper in one script module means every page stamps the same format.
Then cover the other writers. An orchestration that updates the record has to set the same fields in its request body, and so does an EIB load into the business object. Any path that skips them leaves a Last Updated that is quietly wrong. Never compute new created values on an edit: the sample's create page sets createdBy and createdDate, and its edit page sends the stored values of both back unchanged from hidden fields.
These fields are an application-level record. They show the latest change and who made it as your app recorded it; the Workday audit trail remains the authoritative record of changes.
Keeping history you can report as of a date
For values at a point in time, Workday's guidance is direct: because Extend objects can't be effective-dated, use an object that stores a transaction log of changes with dates. The Extend course adds that if you need the details of each previous version, the data model needs separate instances per version. Design it in before go-live, because model components can't be removed from an app after promotion.
A change-log object is usually small: a SINGLE_INSTANCE field pointing at the parent record, a changedBy worker, a changedOn date, and either the changed field's name with old and new values, or a full copy of the fields that matter. Write one row in the same outbound sequence as the update. Reports then use the log object as their primary data source (see reporting on Extend business objects) and traverse to the parent record, which matches Workday's rule that Extend objects must be the primary data source when a report crosses to delivered objects.
Mind the instance budget: a log row per change counts towards the per-tenant limit of 10 million Extend instances (100 million on Extend Professional), shared by every Extend business and attachment object in the tenant.
Purge leaves its own trail
If you purge data from Extend objects for privacy, the purge itself is audited separately. Workday records who ran Purge Person Data and when, which data types were selected, and which report chose the data, visible through View Object, View Task Audit Trail and View User, with View User Activity also showing who and when. Purgeable fields on the log object follow the same rules as on the parent, so mark both if the personal data appears in both.
Check that the trail is complete
- Change one record through each write path the app has: the edit page, each orchestration, and an EIB load. After each, read the record back with
GET /{collection}/{id}and confirmlastUpdatedByandlastUpdatedDatemoved. - Edit as a second user and confirm
lastUpdatedBychanged whilecreatedBydidn't. - If you keep a change log, filter it by the parent record and confirm one row per change with the right before and after values.
- Approve a business process event, then try editing its record. If the event history now shows the edited values, that path needs to go or to write a log row first.
Sources checked
- Reference: Workday-Delivered vs. Workday Extend Business Objects
- Workday Extend for Developers: Model Component Business Objects and Security Domains
- Reference: Reporting and Object Dating Restrictions
- Reference: Design Considerations for Business Processes
- Reference: Reserved Field Names on Business Objects
- Example: Query Parameters in Business Object API Requests
- Display Information on a View Page
- Reference: Predefined App Variables
- Reference: Purged Data Audit Trail
- Reference: Limits on Model Components