ChangeLog Message Types

This document covers the three message types that expose Business Central's Change Log for field-level audit browsing, restoration, and coverage checks.


Overview

Message TypeDirectionDescription
ChangeLog.Field.HistoryOutbound (Read)Returns current value + full modification history for a single field on a record
ChangeLog.Field.RestoreInbound (Write)Restores a field to a previous value from the Change Log (by entry number or point-in-time)
ChangeLog.Field.EnabledOutbound (Read)Checks whether a field is covered by BC Change Log modification tracking
ChangeLog.Records.DeltaOutbound (Read)Returns distinct SystemIds of records changed (Insert/Modify) in a table within a date/time range, optionally filtered to fields

Prerequisite: The BC Change Log feature must be configured for the relevant tables and fields (Administration → Change Log Setup → Tables). Without Change Log entries there is no history to browse or restore.

Typical workflow:

  1. Call ChangeLog.Field.Enabled to confirm a field is tracked.
  2. Call ChangeLog.Field.History to browse modifications and pick the target entryNo.
  3. Call ChangeLog.Field.Restore with that entryNo to write the value back.

ChangeLog.Field.History

Direction: Outbound Object IDs: Codeunit 65415 (ChangeLog Field History Impl), Codeunit 65416 (ChangeLog Field History Help)

Purpose

Returns the current live value of a field together with all Change Log Entry records for that field on a specific record, ordered from the most recent to the oldest.

The response always starts with a synthetic Current entry at entryNo = 0 showing the live field value. This makes it easy for callers to compare the current state against the historical entries without a separate lookup.

Request Format

{
  "specversion": "1.0",
  "type": "ChangeLog.Field.History",
  "source": "MyApp v1.0",
  "data": "{\"tableName\":\"Customer\",\"recordSystemId\":\"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\"fieldNo\":2}"
}

Input Parameters

ParameterTypeRequiredDescription
tableName / tableNumber / tableNo / tableIdstring / integerYesTarget table — name (e.g. "Customer") or number (e.g. 18).
recordSystemId / systemId / idGUID stringYes*SystemId of the record.
fieldNo / fieldId / fieldNameinteger / stringYesThe field to retrieve history for.

*If recordSystemId is missing from the data payload, the CloudEvents subject attribute is used as a GUID fallback.

Response Format

{
  "status": "Success",
  "tableNo": 18,
  "tableName": "Customer",
  "recordSystemId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "fieldNo": 2,
  "fieldName": "Name",
  "fieldType": "Text",
  "history": [
    {
      "entryNo": 0,
      "dateAndTime": "2026-03-28T14:22:00.000Z",
      "typeOfChange": "Current",
      "oldValue": "",
      "newValue": "Contoso Ltd.",
      "userId": "ADMIN"
    },
    {
      "entryNo": 56789,
      "dateAndTime": "2026-03-10T09:00:00.000Z",
      "typeOfChange": "Modification",
      "oldValue": "Contoso Inc.",
      "newValue": "Contoso Ltd.",
      "userId": "ADMIN"
    },
    {
      "entryNo": 45001,
      "dateAndTime": "2025-11-15T11:30:00.000Z",
      "typeOfChange": "Modification",
      "oldValue": "Contoso",
      "newValue": "Contoso Inc.",
      "userId": "JANE"
    }
  ],
  "totalCount": 3
}

Top-Level Response Fields

FieldTypeDescription
statusTextSuccess or Error
tableNoIntegerTable number
tableNameTextTable name
recordSystemIdTextRecord SystemId (GUID without braces)
fieldNoIntegerField number
fieldNameTextField name
fieldTypeTextData type (Text, Code, Decimal, Date, etc.)
historyArrayChange entries, newest first. Index 0 is always the current live state.
totalCountIntegerNumber of entries in history (including the entryNo=0 synthetic entry)

History Entry Fields

FieldTypeDescription
entryNoBigIntegerChange Log Entry No. 0 = current live state (synthetic). Use entryNo > 0 with ChangeLog.Field.Restore.
dateAndTimeDateTimeISO 8601 timestamp. For entryNo=0 this is SystemModifiedAt of the record.
typeOfChangeTextCurrent (index 0 only), Insertion, Modification, or Deletion
oldValueTextValue before the change. Empty for entryNo=0.
newValueTextValue after the change. For entryNo=0 this is the live field value.
userIdTextUser who made the change. For entryNo=0 resolved from SystemModifiedBy GUID.

Error Cases

ErrorCause
Table not foundInvalid table name or number
recordSystemId or a subject GUID is requiredNo record identifier provided
Field identifier requiredNo fieldNo, fieldId, or fieldName given
Field read-restrictedField is blocked in Cloud Events Field Access
Record not foundNo record with the given SystemId

ChangeLog.Field.Restore

Direction: Inbound (Write) Object IDs: Codeunit 65412 (ChangeLog Field Restore Impl), Codeunit 65413 (ChangeLog Field Restore Help), Codeunit 65414 (ChangeLog Field Restore Process)

Purpose

Writes a previous field value back to the live Business Central record. The value is taken from the Old Value of the chosen Change Log entry. The restore is performed using Validate() + Modify(true) so all field-level business logic triggers fire normally.

Supports two modes:

ModeIdentifierDescription
1 — By Entry NumberentryNoRestores the Old Value of a specific Change Log Entry
2 — By Point-in-TimerestoreToDateTimeFinds the most recent Modification entry at or before the timestamp and restores its Old Value

Mode 1: By Entry Number

{
  "specversion": "1.0",
  "type": "ChangeLog.Field.Restore",
  "source": "MyApp v1.0",
  "data": "{\"entryNo\":56789}"
}
ParameterRequiredDescription
entryNoYesChange Log Entry No. from ChangeLog.Field.History. Must be > 0.

Mode 2: By Point-in-Time

{
  "specversion": "1.0",
  "type": "ChangeLog.Field.Restore",
  "source": "MyApp v1.0",
  "data": "{\"tableName\":\"Customer\",\"recordSystemId\":\"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\"fieldNo\":2,\"restoreToDateTime\":\"2026-02-10T09:15:00Z\"}"
}
ParameterRequiredDescription
tableName / tableNumber / tableNo / tableIdYesTarget table
recordSystemIdYesSystemId (GUID) of the record
fieldNo / fieldId / fieldNameYesThe field to restore
restoreToDateTimeYesISO 8601 timestamp — finds the most recent Modification entry at or before this time

Response Format

{
  "status": "Success",
  "tableNo": 18,
  "tableName": "Customer",
  "recordSystemId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "fieldNo": 2,
  "fieldName": "Name",
  "previousValue": "Contoso Ltd.",
  "restoredValue": "Contoso Inc.",
  "fromEntryNo": 56789,
  "entryDateTime": "2026-02-10T09:15:00.000Z"
}
FieldTypeDescription
statusTextSuccess or Error
tableNoIntegerTable number
tableNameTextTable name
recordSystemIdTextRecord SystemId (GUID without braces)
fieldNoIntegerField number
fieldNameTextField name
previousValueTextField value immediately before the restore
restoredValueTextValue written back to the record
fromEntryNoBigIntegerChange Log Entry No. used as the restore source
entryDateTimeDateTimeTimestamp of the Change Log entry (ISO 8601)

Safety Guards

The following conditions must all be satisfied or the restore is rejected:

Error Cases

ErrorCause
Change log entry not foundInvalid entryNo
No change log entry found for the specified record, field, and dateMode 2: no Modification entry at or before the requested timestamp
Only Modification entries can be restoredEntry type is Insertion or Deletion
Record not foundTarget record no longer exists
Field is write-restrictedField blocked by Cloud Events Field Access
Field is not a writable fieldFlowField or FlowFilter
Cannot convert value to field typeType mismatch during Evaluate
Field already has the value — nothing to restoreCurrent value equals restore value; no write attempted
Table is restricted from write operationsBlocked by Cloud Events Setup data-record restrictions
Field is not allowed by the change log write guardBlocked by the ChangeLog Write Guard in Cloud Events Setup
recordSystemId is required for point-in-time restoreMode 2 called without a record identifier
Provide either entryNo or tableName + recordSystemId + fieldNo + restoreToDateTimeNeither Mode 1 nor Mode 2 parameters present

ChangeLog.Field.Enabled

Direction: Outbound Object IDs: Codeunit 65362 (ChangeLog FieldEnabled Impl), Codeunit 65363 (ChangeLog FieldEnabled Help)

Purpose

Checks whether:

  1. The BC Change Log feature is globally activated.
  2. The ChangeLog Write Guard is in an enforcing mode (Blocked or Via force).
  3. The specified field is covered by Change Log Setup for Modification tracking.

Useful before writing via Data.Records.Set when the ChangeLog Write Guard is active, or before calling ChangeLog.Field.Restore to confirm the field has history.

Request Format

{
  "specversion": "1.0",
  "type": "ChangeLog.Field.Enabled",
  "source": "MyApp v1.0",
  "data": "{\"tableName\":\"Customer\",\"fieldNo\":2}"
}
ParameterTypeRequiredDescription
tableName / tableNumber / tableNo / tableIdstring / integerYesTarget table
fieldNo / fieldId / fieldNameinteger / stringYesThe field to check

Response Format

{
  "status": "Success",
  "changeLogEnabled": true,
  "changelogWriteGuardEnabled": true,
  "tableNo": 18,
  "tableName": "Customer",
  "fieldNo": 2,
  "fieldName": "Name",
  "fieldCovered": true,
  "fieldWriteGuardBypassed": false
}
FieldTypeDescription
statusTextSuccess or Error
changeLogEnabledBooleantrue if BC Change Log is globally activated
changelogWriteGuardEnabledBooleanfalse = Open mode (no enforcement); true = Blocked or Via Force
tableNoIntegerTable number
tableNameTextTable caption
fieldNoIntegerField number
fieldNameTextField name
fieldCoveredBooleantrue if the field is tracked by Change Log Setup for modification logging
fieldWriteGuardBypassedBooleantrue if this field has a Bypass entry in Cloud Events Field Access, allowing writes regardless of Change Log coverage

Error Cases

ErrorCause
Table not foundInvalid table name or number
Field identifier requiredMissing fieldNo, fieldId, or fieldName
Field type is not supported for change log trackingBLOB, Media, or similar type that BC cannot track

ChangeLog.Records.Delta

Direction: Outbound Object IDs: Codeunit 65555 (ChangeLog Records Delta Impl), Codeunit 65556 (ChangeLog Records Delta Help)

Purpose

Returns the distinct SystemIds of records in a single table that were Inserted or Modified within a date/time range. Optionally narrows the search to a set of tracked fields. Use this as the source for an incremental sync: pull the changed ids, then fetch each record's full payload via Data.Records.Get and per-field history via ChangeLog.Field.History.

Deletions are not returned — use Deleted.RecordIds.Get for deleted records.

Request Format

{
  "specversion": "1.0",
  "type": "ChangeLog.Records.Delta",
  "source": "MyApp v1.0",
  "subject": "Customer",
  "data": "{\"tableName\":\"Customer\",\"fieldNumbers\":[2,3],\"startDateTime\":\"2026-03-01T00:00:00Z\",\"endDateTime\":\"2026-03-31T23:59:59Z\"}"
}

Input Parameters

FieldTypeRequiredDescription
tableName / tableNumber / tableNo / tableIdText/IntegerYesTable to query. Falls back to subject if not in data.
fieldNumbersInteger[]NoRestricts results to changes on these field numbers. Omit (or empty array invalid) to include any tracked field.
startDateTimeDateTimeNoInclusive lower bound. Defaults to 0DT (no lower bound).
endDateTimeDateTimeNoInclusive upper bound. Defaults to the message Date & Time.

Response Format

{
  "status": "Success",
  "tableNo": 18,
  "tableName": "Customer",
  "fieldNumbers": [2, 3],
  "startDateTime": "2026-03-01T00:00:00.000Z",
  "endDateTime": "2026-03-31T23:59:59.000Z",
  "totalCount": 42,
  "systemIds": [
    "a1b2c3d4-...",
    "e5f6a7b8-..."
  ]
}

Response Fields

FieldTypeDescription
statusTextSuccess or Error
tableNoIntegerResolved table number
tableNameTextResolved table name
fieldNumbersInteger[]Echo of the requested field filter (empty when no filter applied)
startDateTime / endDateTimeDateTimeResolved range applied to Change Log Entry
totalCountIntegerNumber of distinct SystemIds returned
systemIdsGuid[]Distinct SystemIds of changed records, formatted without braces

Error Cases

ErrorCause
Table ... not found.tableName / tableNumber did not resolve to a known table
fieldNumbers must contain at least one integer when supplied.fieldNumbers was supplied as an empty array

Typical Workflow

  1. Call ChangeLog.Records.Delta with the table, range and (optionally) the field set to track.
  2. For each returned SystemId, call Data.Records.Get to fetch the current row.
  3. For deeper audit, call ChangeLog.Field.History per field of interest.

ChangeLog Write Guard

The ChangeLog Write Guard is a field in Cloud Events Setup (field 17) that controls which fields Data.Records.Set may write to. It does not affect read operations.

ModeCaptionEnum ValueBehaviour
OpenOpen0All fields may be written. Default.
BlockedBlocked1Only fields covered by Change Log Modification tracking are writable.
Via forceVia force2Same as Blocked; bypassed when force: true is in the request and the caller has CE Force Access

Enabling the Guard

Changing to Blocked or Via force requires the BC Change Log to be active. The setup validation ensures this and returns an error otherwise.

Using force bypass (Via force mode only)

Include "force": true as a top-level key inside the data JSON alongside the data array:

{
  "specversion": "1.0",
  "type": "Data.Records.Set",
  "source": "MyApp v1.0",
  "subject": "Customer",
  "data": "{\"force\":true,\"data\":[{\"id\":\"...\",\"fields\":{\"Name\":\"Updated Name\"}}]}"
}

The caller must hold the CE Force Access permission set (PermissionSet 65303). Without it, force: true is ignored and the request is rejected in the same way as Blocked.

Checking field coverage before writing

To prevent rejected writes at runtime, call ChangeLog.Field.Enabled first:

{ "type": "ChangeLog.Field.Enabled", "data": "{\"tableName\":\"Customer\",\"fieldNo\":2}" }

If fieldCovered is false and the guard is Blocked or Via force, the write to that field will be rejected unless force: true is used (Via force mode only).


Related Documentation


© Origo – Cloud Events Base Extension