Dataverse Auditing Explained: Environment, Table, and Column-Level Tracking (With a Hands-On Example)

If you’ve ever needed to answer “who changed this field, and what did it say before?” inside a Dataverse-backed app, you’ve needed auditing. It’s one of those features that looks trivial in the maker portal — a couple of checkboxes — but has enough cascading rules, gotchas, and storage implications that it’s worth understanding properly before you flip the switches in a production environment.

This guide walks through how Dataverse auditing actually works, what the default state is at each level, sets up a small sample table so the concepts have something real to attach to, and then enables auditing one layer at a time — so you can see what each layer adds rather than just reading about it.

Why Dataverse Auditing Exists

Dataverse auditing exists to answer governance and compliance questions: who created or changed a record, which columns changed, what the previous value was, who accessed the system, and who deleted something. It’s not a general-purpose history feature — it’s purpose-built for internal and external compliance, security reviews, and regulatory governance, and it stores its data in a separate log storage tier rather than regular database storage.

That last detail matters more than it sounds. Because audit data lives in log storage, it consumes its own capacity allocation (separate from database and file capacity), and entries can show up with a short delay in the Audit History tab or Audit Summary view rather than appearing instantly.

The Three Layers of Auditing — Defaults and Cascade Rules

Auditing in Dataverse is configured at three levels, and each level gates the one below it:

  1. Environment level — the master switch. Nothing below this matters until it’s on.
  2. Table level — turns on auditing for a specific table, but only takes effect if the environment switch is on.
  3. Column level — the most granular layer. A column’s changes are only captured if both the table and the environment are also enabled for auditing.

Default State at Each Level

Before you touch anything, here’s what Dataverse ships with out of the box:

LevelDefault stateNotes
EnvironmentOffAuditing is disabled in all newly provisioned environments. You must explicitly turn it on.
Table (standard)Varies by tableMany core Dynamics 365 tables (Account, Contact, Lead, Opportunity, etc.) ship with table-level auditing pre-enabled. Custom tables you create default to off.
ColumnOn for most columnsWhen you add a column to a table that has auditing enabled, the column’s own audit checkbox defaults to on — meaning the new column is tracked from creation. You have to explicitly opt out at the column level.

That last default surprises people the most. It means: once a table is audited, newly added columns are captured automatically unless you deliberately turn them off. The column-level setting exists primarily for exclusion, not inclusion.

The Cascade Dependency

This cascading dependency is the single most common point of confusion (and a frequent exam trap): enabling auditing on a column does absolutely nothing if the table isn’t audited, and enabling a table does nothing if the environment isn’t audited. Turning off any layer silently disables everything underneath it — Dataverse won’t warn you that your carefully configured column settings are now inert.

You need the System Administrator or System Customizer security role (or an equivalent custom role) to change any of these three settings.

Here’s how the dependency chain looks for the sample table we’ll build later in this post:

LEVEL 1 · ENVIRONMENT — default: OFFPower Platform admin center → Environment Settings → Audit and logs✓ Start Auditing ✓ Log access (optional) ✓ Read logs → Purview (optional)Retention policy: e.g. 90 days, or Foreverrequired before Level 2 has any effectLEVEL 2 · TABLE — default: OFF for custom tablesVendor Invoice tableTable properties → Advanced options → “Audit changes to its data” = ONrequired before Level 3 has any effectLEVEL 3 · COLUMN — default: ON for most columnsPayment Status — auditing ONInvoice Amount — auditing ONIs Confidential — auditing ONApprover Notes — auditing OFF (deliberate)

What Actually Gets Logged — and What Doesn’t

A subtlety that trips people up: auditing doesn’t capture every interaction with a record — only specific operations, and only when the new value of a column differs from the old value.

✓ Logged by Dataverse Auditing• Create, Update, and Delete operations• Column value changes (old → new)• Changes to record sharing privileges• N:N association / disassociation• Changes to security roles• Deletion of audit logs themselves• User sign-in, if “Log access” is onUpdates are only logged when the newvalue differs from the old value.✗ Not Logged by Standard Auditing• Retrieve / read operations• Export to Excel• Table or column definition changes• Authentication eventsReads/exports are covered separatelyby activity logging → Microsoft Purview,not by the Dataverse Audit History tab.A short list of noncustomizable systemtables (e.g. ActivityPointer, Annotation,Workflow, KbArticle) can’t be auditedat all, regardless of settings.

The Practical Example: A “Vendor Invoice” Table

Concepts stick better with a concrete example, so let’s build a small custom table and audit it incrementally — enabling one layer at a time and checking what we can see at each stage. The incremental approach mirrors what happens in real implementations: you’re often called in to troubleshoot “the audit trail isn’t working” and the root cause is almost always a layer that wasn’t turned on.

Table: Vendor Invoice (demo_vendorinvoice)

Display NameSchema NameData TypePurpose
Invoice Numberdemo_invoicenumberText (Primary column)Unique invoice identifier, e.g. INV-1001
Vendor Namedemo_vendornameTextSupplier name
Invoice Amountdemo_invoiceamountCurrencyTotal invoice value
Invoice Datedemo_invoicedateDate OnlyDate the invoice was issued
Payment Statusdemo_paymentstatusChoice (Pending, Approved, Paid, Rejected)Approval workflow state
Approver Notesdemo_approvernotesMultiline TextFree-text internal comments
Is Confidentialdemo_isconfidentialYes/NoFlags sensitive vendor contracts

We’ll enable auditing on Payment Status, Invoice Amount, and Is Confidential — the fields a financial controller actually cares about — and deliberately leave Approver Notes unaudited, as if it were considered low-value noise for the audit trail. But we’ll get there incrementally, starting from zero.


Phase 1: Environment-Only Auditing (Nothing Works Yet)

Step 1 — Turn On Auditing at the Environment Level

This is the master switch, and there are two equivalent ways to set it.

Option A — Power Platform admin center, Compliance page (good for turning on auditing across a standard set of common entities in one action):

  1. Sign in to the Power Platform admin center.
  2. Go to Security → Compliance, select the Auditing tile, then choose your environment.
  3. Select Set up auditing → Turn on auditing.
  4. Optionally select Common entities across Dynamics 365 apps to bulk-enable a standard set of tables (Account, Contact, Lead, and similar).
  5. Set the Event log retention period to match your data retention policy.

Option B — Environment Settings (the route we’ll use for our custom table):

  1. In the admin center, go to Manage → Environments, select your environment, then Settings.
  2. Expand Audit and logs → Audit settings.
  3. Set the retention period (e.g., 90 days for this exercise).
  4. Select Global Audit Settings, turn on Start Auditing, and save.

A couple of settings worth understanding here:

  • Log access records every sign-in/system access event.
  • Read logs sends activity-log data — record reads and exports — to the Microsoft Purview compliance portal, a separate destination from the Dataverse Audit History tab. This option only appears once minimum Microsoft 365 licensing requirements are met, and is sent for production environments only.
  • Setting retention to Forever means nothing is ever auto-deleted; any other value starts a rolling deletion the moment a record crosses that age.

Environment-Level Auditing Settings at a Glance

SettingWhat It CapturesWhere Data Is StoredVisible InNotes
Start AuditingCreates, updates, and deletes on audited tablesDataverse log storage (separate from database capacity)Audit History tab (per record) & Audit Summary View (environment-wide)Master switch — nothing works without this. Table and column auditing must also be enabled separately.
Log AccessUser sign-in and system access eventsDataverse log storageAudit Summary View only — not the per-record Audit History tabIndependent of table/column configuration. Enable if you need to answer “who accessed the environment and when?”
Read LogsWho viewed or exported recordsMicrosoft Purview (not Dataverse)Microsoft Purview portal — completely separate from DataverseRequires: (1) minimum Microsoft 365 E1 license, (2) production environment only, (3) Enable SAS Logging in Purview toggled on under Power Platform Admin Center → Settings → Product → Privacy + Security. If column auditing is later disabled, Purview will show * instead of before/after values.

Important: Enabling Read Logs alone is not enough to see data in Purview. You must also enable Enable SAS Logging in Purview per environment under Power Platform Admin Center → Settings → Product → Privacy + Security.

What You Can See After Phase 1

With only the environment switch on, create a new Vendor Invoice record (INV-1001) and then open its Related → Audit History tab.

You’ll see: nothing. The Audit History tab is empty.

This is the expected result and an important lesson: the environment switch is necessary but not sufficient. With table-level auditing still off (the default for custom tables), no data is being captured for Vendor Invoice regardless of the environment setting. The environment switch just means “auditing could work in this environment” — it doesn’t make it happen for any specific table.

Diagnostic tip: If you’re troubleshooting a missing audit trail in a real environment, the very first thing to verify is that the environment switch is on. It’s easy to miss in a newly provisioned or restored environment where defaults reset.


Phase 2: Add Table-Level Auditing (Row Operations Appear)

Step 2 — Turn On Table-Level Auditing

  1. Open Power Apps maker portal, select your environment.
  2. Go to Tables, select Vendor Invoice, then Edit table properties.
  3. Expand Advanced options and check Audit changes to its data.
  4. Save and publish.

If you’re customizing inside a solution (recommended for any real implementation — it keeps your audit configuration exportable and tied to your publisher prefix), add the table to your solution first, then perform these steps from inside the solution before publishing.

What You Can See After Phase 2

Now go back and create a new record (or re-open INV-1001 if you already have one):

  • Invoice Number: INV-1001
  • Vendor Name: Fabrikam Supplies
  • Invoice Amount: $4,500.00
  • Invoice Date: today
  • Payment Status: Pending
  • Is Confidential: No
  • Approver Notes: Awaiting manager approval

Open Related → Audit History on the record.

You’ll now see a Create entry. It shows your user, a timestamp, and the fact that the record was created — but it does not show a list of individual field values captured at creation. That’s normal: the Create event is a row-level event.

Now change Payment Status from Pending to Approved and save. Check Audit History again.

You’ll see the Update entry — but with no column detail. The entry will show that a change happened, but because no columns have column-level auditing configured yet, Dataverse doesn’t know which specific columns to track. The row changed; which fields changed is not recorded.

This is the key behavior that Phase 2 exposes: table-level auditing alone tells you that a record was touched and by whom, but not what specifically changed. For “who deleted this?” questions, that’s often enough. For “who changed the invoice amount, and what was it before?” — you need Phase 3.

Reminder about column defaults: When you first enabled the table in Step 2, all columns technically have column-level auditing defaulting to on. However, this only takes effect going forward — records created or changed before table-level auditing was enabled won’t have retroactive history. And more practically: you won’t see old-value/new-value detail in Audit History until you’ve verified the column-level settings in the next phase.


Phase 3: Add Column-Level Auditing (Field Changes Captured)

Step 3 — Configure Column-Level Auditing

  1. Still inside the Vendor Invoice table, go to Columns.
  2. Open Payment Status, expand Advanced options, verify Enable auditing is checked (it should default to on), save.
  3. Repeat for Invoice Amount and Is Confidential.
  4. Open Approver Notes and uncheck Enable auditing — this is intentional. We’re treating this field as low-value noise for the audit trail.
  5. Publish all customizations.

Why uncheck Approver Notes explicitly? Remember the default: columns default to auditing on when their table is audited. If you want a column excluded, you have to opt it out deliberately. This split configuration — some columns tracked, some not — is exactly the kind of selective decision real solutions make, and it’s the easiest way to prove that column-level auditing is genuinely independent.

What You Can See After Phase 3

Now make a change to Payment Status from Pending to Approved and save.

You’ll now see full before/after detail in Audit History — the field name, the old value (Pending), the new value (Approved), who made the change, and when.

Make another change to Invoice Amount from $4,500.00 to $4,750.00 and save.

Again, a new entry with old and new values appears.

Now change Approver Notes to Confirmed with finance lead and save.

Nothing new appears in Audit History for this change. This is the proof that column-level auditing is genuinely independent: the table is being audited, the record clearly changed, but because Approver Notes has auditing disabled, no entry is generated for it. If you saw an entry here, your column-level configuration didn’t take.

Here’s roughly what the Audit History looks like after Phases 1, 2, and 3 combined:

Audit History — INV-1001 (Fabrikam Supplies)FIELD / EVENTOLD VALUENEW VALUEMODIFIED BYPHASEPhase 1 (env only): Audit History tab is empty — no entries generatedPhase 2 (env + table): Create and Update events visible, but no field detailRecord CreatedS. SharmaPhase 2Record Updated(no field detail)(no field detail)S. SharmaPhase 2Phase 3 (env + table + columns): Full before/after values capturedPayment StatusPendingApprovedS. SharmaPhase 3Invoice Amount$4,500.00$4,750.00S. SharmaPhase 3Approver Notesnot loggedcolumn auditing disabledGreyed row is shown for contrast only — Approver Notes never appears in the real Audit History grid.Phase 2 “Record Updated” row shows the event existed but field values weren’t captured until Phase 3.

Additional Tests at Full Configuration

With all three layers active, a few more behaviors are worth verifying.

Test: Share the Record

Share the INV-1001 record with another user (or team), granting Read access.

Expected result: An audit entry is logged reflecting the change to sharing privileges — this is tracked at the table level automatically and doesn’t require a specific column to be enabled.

Test: Delete the Record

Delete INV-1001.

Expected result: Because the record itself no longer exists, you can’t open its Audit History tab anymore. Instead, go to the environment-wide Audit Summary view (via Power Platform Environment Settings app → Advanced Settings → System → Auditing → Audit Summary view) and filter by table or user. You’ll find a Delete entry for INV-1001 there — this is the main reason the Audit Summary view exists: it’s the only place to see audit trail for records that are no longer in the system.

Test: No-op Update

Open a record, change Payment Status to Approved, then immediately change it back to Pending and save in a single operation.

Expected result: No audit entry is created, because the saved value is identical to the previous saved value. Auditing evaluates the delta at save time, not the edit history within a session.


Verifying Programmatically

For automated tests or integrations, you don’t have to click through the UI — Dataverse exposes dedicated messages for retrieving audit data via the Web API or the .NET SDK:

MessageWhat it returns
RetrieveRecordChangeHistoryThe full change history for all audited columns of a single record
RetrieveAttributeChangeHistoryThe change history for one specific column of a record
RetrieveAuditDetailsThe complete detail of a single audit record

A minimal Web API call to pull a record’s full change history looks like this:

GET [Organization URI]/api/data/v9.2/demo_vendorinvoices(<record-guid>)/Microsoft.Dynamics.CRM.RetrieveRecordChangeHistory

This is the approach to use if you want to write a regression test that asserts “changing Payment Status produces exactly one audit entry, changing Approver Notes produces zero” — essentially automating the Phase 3 tests above.

You can’t delete individual audit rows directly through the Audit table, though — deletion only happens through dedicated messages: DeleteRecordChangeHistory (wipes a single record’s trail), BulkDelete (asynchronous, query-based, for large volumes), and DeleteAuditData (specific to environments using customer-managed encryption keys).


Managing Retention and Storage

A few behaviors around retention and cleanup are easy to get wrong:

  • Retention changes aren’t retroactive. Each audit log is stamped with whatever retention period was active when it was created. If you change the policy from 30 to 90 days, existing logs keep expiring on the old 30-day clock — only new logs get 90 days.
  • The maximum custom retention is 24,855 days, and the option isn’t available at all for Dynamics 365 Customer Engagement (on-premises) or environments using customer-managed encryption.
  • Deletion is irreversible — once an audit log is deleted, that period’s history is gone for good.
  • There’s both a legacy deletion process (delete only the single oldest log, repeatedly) and a newer process that lets you delete by table, delete all access logs, or delete everything up to a chosen date — all as asynchronous background jobs you can monitor through the Bulk Deletion view. Expect roughly 100 million records deleted per day (about 4 million per hour).
  • Large text fields are capped at 5 KB (~5,000 characters) in audit storage — a truncated value ends in three dots, e.g. “Confirmed with finance lead, pending fi…”.
  • If you turn off auditing on a column that was previously audited, Purview activity logs will show before/after values as * going forward rather than silently omitting the entry.

Dataverse Audit History vs. Microsoft Purview Activity Logging

These two systems get confused constantly because they’re both called “auditing,” but they serve different purposes and live in different places:

Dataverse Audit HistoryMicrosoft Purview (via Read logs)
CapturesCreate / Update / Delete / Share / Associate / security role changesRecord reads and exports, plus optional sign-in access
Where you view itAudit History tab (per record) and Audit Summary view (per environment)Microsoft Purview compliance portal
Typical use case”What changed on this record, and who changed it?”Centralized, cross-Microsoft-365 compliance review of who viewed or exported data
RequiresEnvironment + table + column auditing enabledEnvironment-level “Read logs” + minimum Microsoft 365 licensing

For most in-app, day-to-day scenarios, table- and column-level auditing with the Audit History tab is all you need. Reach for Purview activity logging when your compliance team needs centralized visibility across Microsoft 365 and Power Platform together, or needs to track read access rather than just changes.


Common Pitfalls

A short list of things that tend to catch people off guard:

  • Forgetting the cascade. Re-enabling a column after a table was temporarily turned off doesn’t backfill anything, and if the table or environment switch is off, the column setting is just sitting there inert.
  • Misreading the column default. Columns default to auditing on, but that only has any effect after the table is audited. Many people interpret “I didn’t configure the column” as “the column isn’t audited” — it’s the opposite. If you want a column excluded, you have to explicitly disable it.
  • A small set of system tables can never be audited, regardless of configuration — examples include ActivityPointer, Annotation, Workflow, KbArticle, and Resource. If a table doesn’t expose the audit checkbox, this is usually why.
  • Choice (picklist) values audit by current label, not by the label active at the time of the change — unless you explicitly set the StoreLabelNameforPicklistAudits organization setting to true. Without it, renaming a choice option later will rewrite how old audit entries display, which can be surprising during a compliance review.
  • Manage audit configuration inside a solution, not directly against the default/unmanaged layer — it keeps the setup portable across environments and tied to your publisher prefix, which matters the moment you need to promote the configuration through dev/test/prod.
  • Audit Summary view filtering has known quirks — the Record column filter doesn’t work, and the Entity column’s Equals/Does not equal options don’t populate values; use Contains and type the table name instead.
  • Exporting audit logs isn’t supported from the UI at all — if you need the data outside Dataverse, you’re going through the Web API/SDK messages, or linking the Audit table via Azure Synapse Link for Power BI reporting.
  • No retroactive fill. Enabling auditing at any level — environment, table, or column — only starts capturing from that moment forward. Changes made before auditing was enabled are gone.

Quick Reference

Default States

LevelDefaultWhat it means in practice
EnvironmentOffAll newly provisioned environments start with auditing disabled
Table (custom)OffYour custom tables aren’t audited until you explicitly enable them
Table (standard Dynamics 365)On for manyCore tables like Account and Contact ship with auditing pre-enabled
ColumnOnColumns default to being audited once their table is audited — opt out explicitly if needed

Configuration Locations

LayerWhere to configureDepends onTypical role required
EnvironmentPower Platform admin center → Environment Settings, or Compliance pageNothing — top of the chainSystem Administrator / System Customizer
TableMaker portal → Table → Edit table properties → Advanced optionsEnvironment auditing ONSystem Administrator / System Customizer
ColumnMaker portal → Table → Columns → Edit column → Advanced optionsTable and environment auditing ONSystem Administrator / System Customizer

Where to View Results

ActionWhere to view it
Single record’s historyAudit History tab on the record (needs View Audit History privilege)
All audit logs, environment-wideAudit Summary view (needs View Audit Summary privilege)
Deleted record’s historyAudit Summary view only — the record’s own tab no longer exists
Programmatic retrievalRetrieveRecordChangeHistory, RetrieveAttributeChangeHistory, RetrieveAuditDetails

Wrapping Up

The mechanics of Dataverse auditing are simple once you’ve seen them work end to end: three cascading switches with distinct defaults, a defined set of operations that get captured, and two places to go looking for the result depending on whether the record still exists.

The incremental Vendor Invoice walkthrough above deliberately surfaces what each layer adds on its own — an empty Audit History tab with only the environment on, row-level events with no field detail once the table is enabled, and finally full before/after column values once column-level auditing is confirmed. That progression is the fastest way to internalize why all three switches matter, and it’s also your mental model for diagnosing a broken audit trail in production: check the environment, then the table, then the columns, in that order.

Comments