Wicked Smart Data
LearnInsightsAboutContact
Sign InLet's Build
LearnInsightsAboutContact
Sign InLet's Build
Wicked Smart Data

Intelligence, automation, and expert execution — plus an elite library of free knowledge. We turn complexity into competitive advantage.

Start a conversation

Platform

  • Learning Paths
  • Insights
  • RSS Feed

Company

  • About
  • Contact
  • Work With Us

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Wicked Smart Data. All rights reserved.

Intelligence · Automation · Advantage

All Insights
Power Apps

Auditing and Change Tracking in Dataverse for Compliance

Learn how to design and implement a complete Dataverse audit strategy that satisfies GDPR, SOX, and HIPAA requirements. This expert lesson covers the three-layer audit architecture, Web API queries, real-time alerting, storage management, and the common gaps that leave organizations exposed during audits.

🔥 Expert31 min readSep 22, 2026Updated Sep 22, 2026
Auditing and Change Tracking in Dataverse for Compliance
On this page
  • Introduction
  • Prerequisites
  • Understanding the Audit Architecture: Three Layers, One Coherent System
  • What Dataverse Actually Captures
  • Configuring Auditing: Step by Step
  • Environment-Level Configuration
  • Table-Level Configuration
  • Column-Level Configuration
  • Audit Data Internals: What's Happening Under the Hood
  • Querying Audit Data with the Web API
  • Basic Audit Query
  • Filtering by Table Type
  • Querying by User
  • Retrieving Audit History Through the SDK Action
  • Parsing changedata for Analysis
  • Surfacing Audit History in Model-Driven Apps
  • Enabling the Audit History Tab on a Form
  • Security Privileges for Audit Access
  • Designing an Audit Strategy for Real Compliance Frameworks
  • GDPR Considerations
  • SOX Considerations
  • HIPAA Considerations
  • Audit Storage Management and Retention Policies
  • How Much Storage Will Audit Use?
  • Archiving Audit Data
  • Manually Purging Audit Records
  • Real-Time Monitoring and Alerting
  • Power Automate-Based Alerting
  • Building an Audit Report in Model-Driven Apps
  • Creating a Custom View for Audit Queries
  • Hands-On Exercise
  • Scenario
  • Step 1: Enable Environment-Level Auditing
  • Step 2: Configure Table-Level Auditing
  • Step 3: Configure Column-Level Auditing
  • Step 4: Verify Audit Capture
  • Step 5: Build an Alert Flow
  • Step 6: Validate Security
  • Common Mistakes & Troubleshooting
  • Mistake 1: Enabling Auditing But Not Publishing
  • Mistake 2: Assuming Related Records Are Covered
  • Mistake 3: Missing the Change in Lookup Values
  • Mistake 4: Forgetting System Processes
  • Mistake 5: Not Testing Read Access Before a Compliance Review
  • Troubleshooting: Audit Records Not Appearing
  • Troubleshooting: Storage Quota Alerts
  • Summary & Next Steps
  • Where to Go Next
  • Auditing and Change Tracking in Dataverse for Compliance

    Introduction

    Picture this: it's 9 AM on a Tuesday and your legal team is on the phone. A customer record was modified — the credit limit was quietly raised from $50,000 to $500,000 three weeks ago, and nobody can say who did it or when. The deal that followed went sideways. Now the auditors want a full account of every change to that record for the past six months. Can you produce it?

    If you've configured Dataverse auditing correctly, the answer is yes — and you can pull that report in minutes. If you haven't, you're in for an uncomfortable conversation. Dataverse's built-in audit framework is one of the most powerful and underappreciated capabilities in the platform. It's deeply integrated, surprisingly granular, and — when designed correctly — capable of meeting GDPR, SOX, HIPAA, and ISO 27001 traceability requirements without a single line of custom code. But "built-in" doesn't mean "automatic." You have to understand how auditing works at each layer, make deliberate design choices, and avoid the gaps that trip up even experienced architects.

    By the end of this lesson, you'll understand the complete Dataverse audit architecture — from the environment-level switch all the way down to individual column tracking. You'll know how to query audit data programmatically, surface it in model-driven apps, manage storage and retention, and design your audit strategy around real compliance requirements rather than theoretical best practices.

    What you'll learn:

    • How Dataverse auditing is structured across three configuration layers (environment, table, column) and why each layer matters
    • How audit data is stored internally, what gets captured and what doesn't, and the performance implications of your choices
    • How to retrieve and analyze audit history using both the Power Apps UI and the Dataverse Web API
    • How to design a defensible audit strategy that satisfies GDPR, SOX, and other frameworks
    • How to manage audit log storage, retention policies, and the common failure modes that leave compliance gaps

    Prerequisites

    This lesson assumes you're already comfortable with the core Dataverse object model — tables, columns, relationships, and environments. If you need a refresher on how tables and columns are structured, start with Dataverse Fundamentals: Tables, Columns, and Rows Explained for Power Apps Makers before continuing. You should also have a working understanding of security roles and how they gate access in Dataverse, since auditing and access control are deeply intertwined. The Dataverse Security: Business Units, Security Roles, and Teams lesson is a strong complement to this one.

    You'll need System Administrator or System Customizer privileges in your environment to configure audit settings. Some sections of this lesson also use the Dataverse Web API, so familiarity with REST calls and JSON will help.


    Understanding the Audit Architecture: Three Layers, One Coherent System

    Before you touch a single setting, you need to understand the mental model. Dataverse auditing is governed by three nested layers:

    1. Environment-level auditing — the master on/off switch for the entire environment
    2. Table-level auditing — enables or disables audit capture for a specific table
    3. Column-level auditing — controls which individual columns within a table are tracked

    Think of these as nested gates. If the environment switch is off, nothing is audited, regardless of table or column settings. If environment auditing is on but table auditing is off for your Accounts table, changes to account records produce no audit logs. And if both environment and table auditing are on, but you haven't enabled audit on the "Credit Limit" column, modifications to that field won't appear in the log.

    This layered design is intentional and smart — it lets you be surgical about what you track, which matters enormously when you consider that Dataverse audit logs consume storage from your organization's Dataverse capacity.

    Key insight

    The three-layer architecture means you can audit selectively. An organization storing 50 million contact records doesn't need to audit every phone number edit. You audit what has compliance value — financial fields, status changes, personally identifiable information — and leave commodity fields alone.

    What Dataverse Actually Captures

    When an audited column on an audited table in an audited environment changes, Dataverse writes a record to the internal audit table. Each audit record contains:

    • The record ID and table name that was changed
    • The operation type: Create, Update, Delete, Access (read), and a few system operations like attribute changes
    • The old value and new value for every audited column that changed in that operation
    • The timestamp of the change (UTC, precise to milliseconds)
    • The user ID of the caller — the Entra ID (Azure AD) user who made the API call
    • Whether the change was made by a user directly or by another application (service principal)

    Notice what's not on that list: IP addresses, device information, or session context. If your compliance framework requires client IP capture (some do), you'll need to supplement Dataverse auditing with Azure Active Directory sign-in logs or Microsoft Purview.

    Warning

    The "user" captured in an audit record is the Entra ID identity making the API call — not necessarily the person sitting at the keyboard. If a Power Automate flow runs under a service account, all changes attributed to that flow will show the service account's identity. For compliance purposes, your automation designs need to account for this: either run flows under per-user connections, or capture the originating human context in a separate column.

    Access auditing (tracking reads, not just writes) is a separate option. Because read operations are vastly more frequent than writes, enabling access auditing can generate enormous log volumes and significant storage consumption. Most organizations enable access auditing only for their most sensitive tables — medical records, compensation data, financial forecasts.


    Configuring Auditing: Step by Step

    Environment-Level Configuration

    To enable auditing at the environment level, navigate to the Power Platform admin center (admin.powerplatform.microsoft.com), select your environment, then open Settings → Audit and logs → Audit settings.

    You'll see three main controls here:

    • Start Auditing: The master enable/disable switch
    • Audit user access: Enables session-level user access tracking (logs when users sign in)
    • Log access: Enables record-level read auditing across all tables that have access auditing enabled at the table level

    Turn on "Start Auditing." Leave "Log access" off unless you have a specific requirement — we'll revisit this when discussing storage management.

    Within the same settings panel, you'll find the retention period dropdown. This is critically important. Options typically include 30 days, 90 days, 1 year, and — in some licensing tiers — indefinite. The retention period controls how long audit records persist before the system automatically purges them. If your compliance framework requires 7-year retention (as SOX can require for certain records), you need a separate archival strategy, not just the default Dataverse retention setting.

    Warning

    Retention period settings take effect immediately and apply retroactively to existing audit data. If you accidentally set retention to 30 days in an environment that's been accumulating audit logs for two years, you will lose historical data. Treat this setting with the same care as a production database truncation.

    Table-Level Configuration

    Navigate to make.powerapps.com, select your environment, then open the Data section and find the table you want to configure. Open the table properties and select the "Advanced options" section (in the classic editor, this is the table Properties dialog; in the modern editor, it's within the table settings panel).

    You'll find the "Audit changes to its data" checkbox. Enabling this begins capturing audit records for all subsequently configured columns on this table. It does not retroactively create audit records for past changes.

    Two important things to know about table-level audit configuration:

    First, certain system tables have auditing pre-configured and cannot be disabled — the audit log itself, security role assignments, and a handful of others are always tracked by the platform.

    Second, table auditing behavior on related records is independent. If you audit the Account table and a user changes a contact's email address on a subgrid within an account form, the audit record for that change is attached to the Contact table, not the Account. Understanding this parent-child boundary prevents gaps in your audit trail.

    Column-Level Configuration

    Within the same table editor, navigate to each column and open its properties. Under "Advanced options" (or in the column's properties in the classic editor), you'll find "Enable auditing."

    Column-level auditing defaults vary by column type. Primary name columns are often audited by default. System columns like createdon, modifiedon, and owner fields may or may not be audited by default, depending on the table.

    The strategic question is: which columns do you actually need to audit? Here's a practical framework:

    Always audit:

    • Financial values (amounts, credit limits, payment terms)
    • Status and state columns (record status, approval states)
    • Owner/responsible party assignments
    • Columns that gate access or permissions
    • Any column that is a key data point in regulatory filings

    Audit selectively based on risk:

    • Email addresses and phone numbers (PII — depends on your GDPR posture)
    • Addresses and demographic data
    • Contractual terms and dates

    Usually skip:

    • Formatting preferences
    • Internal notes and non-consequential description fields (unless document-based regulations apply)
    • Computed or derived fields that are already traceable through their source columns

    Tip

    When in doubt, err toward auditing more columns during initial configuration. You can always remove audit from a low-value column later (though you'll lose future log entries, not past ones). It's much harder to explain to an auditor why critical column changes weren't tracked than to manage slightly larger storage consumption.


    Audit Data Internals: What's Happening Under the Hood

    Understanding the internal structure of audit data helps you write better queries and reason about performance.

    Dataverse stores audit records in the audit system table (logical name: audit). You can't add columns to this table, but you can query it through the Web API and the SDK. Each row has these key attributes:

    • auditid — unique GUID for the audit record
    • objectid — the GUID of the record that changed
    • objecttypecode — the entity type code of that record's table
    • userid — the GUID of the user who made the change
    • createdon — when the audit record was written
    • action — an integer representing the operation type (see below)
    • operation — another integer for the operation category (Create=1, Update=2, Delete=3, Access=4)
    • changedata — a JSON blob containing the old and new values for changed columns

    The changedata column is where the actual diff lives. It's stored as serialized JSON, which means:

    • You can read it
    • You cannot filter on it in OData queries (no $filter against nested JSON)
    • Parsing it requires client-side processing or Power Automate expressions

    This has significant implications for how you build audit reports. You can filter audit records by objectid, userid, action, createdon, and objecttypecode server-side. But if you want to find "all records where the Credit Limit changed by more than 20%," you need to pull the relevant audit records client-side and parse changedata yourself.

    The action values cover more ground than you'd expect. Beyond Create/Update/Delete, you'll encounter values for:

    • 65 — Delete audit records (someone purging the audit log)
    • 64 — Disable auditing
    • 63 — Enable auditing
    • 52 — Merge
    • 54 — Assign

    These meta-audit entries are enormously valuable for compliance: they tell you if someone tried to cover their tracks by disabling auditing or deleting logs.

    Key insight

    Dataverse audits the audit system itself. Action codes 64 and 65 mean that if someone disables auditing or purges audit records, that act is itself recorded — unless, of course, they delete those records too. A complete compliance implementation should alert on action codes 64, 65, and 101 (delete audits) using a Power Automate flow.


    Querying Audit Data with the Web API

    The Power Apps model-driven UI shows audit history on individual records, which is useful for day-to-day use. But for compliance reporting, investigation, and bulk analysis, you need the Web API.

    Basic Audit Query

    Here's a query that retrieves all audit records for a specific account, ordered newest-first:

    GET https://yourorg.crm.dynamics.com/api/data/v9.2/audits?
      $filter=objectid/accountid eq 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
      &$orderby=createdon desc
      &$select=auditid,createdon,userid,action,operation,changedata
      &$top=50
    Accept: application/json
    OData-MaxVersion: 4.0
    OData-Version: 4.0
    

    This returns a paged JSON response. The changedata property in each record contains the before/after snapshot.

    Filtering by Table Type

    To get audit records for all changes to the Account table across your environment:

    GET https://yourorg.crm.dynamics.com/api/data/v9.2/audits?
      $filter=objecttypecode eq 'account'
      &$orderby=createdon desc
      &$select=auditid,createdon,userid,action,operation,objectid,changedata
    

    Note

    The objecttypecode filter uses the logical name of the table (e.g., account, contact, opportunity), not the display name or schema name.

    Querying by User

    For HR investigations or user offboarding reviews, you'll want all changes made by a specific user:

    GET https://yourorg.crm.dynamics.com/api/data/v9.2/audits?
      $filter=userid/systemuserid eq 'user-guid-here'
      &$orderby=createdon desc
    

    Retrieving Audit History Through the SDK Action

    The Web API also exposes a special action for retrieving formatted audit history: RetrieveRecordChangeHistory. This is the same action the model-driven app calls internally when you view audit history on a form. It returns a more structured response than the raw audits endpoint, including formatted column labels and human-readable values.

    POST https://yourorg.crm.dynamics.com/api/data/v9.2/RetrieveRecordChangeHistory
    Content-Type: application/json
    
    {
      "Target": {
        "accountid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "@odata.type": "Microsoft.Dynamics.CRM.account"
      },
      "PagingInfo": {
        "PageNumber": 1,
        "Count": 50
      }
    }
    

    The response includes an AuditDetailCollection with typed AttributeAuditDetail entries, each containing the OldValue and NewValue as full entity representations. This is significantly easier to parse for display purposes than raw changedata.

    Parsing changedata for Analysis

    When you need to analyze changedata at scale — say, identifying all records where a particular field changed — you'll need a parsing strategy. In Power Automate, this typically looks like:

    // Parse changedata JSON in a Power Automate Compose action
    @{json(triggerBody()?['changedata'])}
    

    Then access individual old/new values with expressions like:

    outputs('Parse_ChangeData')?['changedAttributes'][0]['oldValue']['Value']
    

    The structure of changedata is:

    {
      "changedAttributes": [
        {
          "logicalName": "creditlimit",
          "oldValue": { "Value": 50000 },
          "newValue": { "Value": 500000 }
        },
        {
          "logicalName": "paymentterms",
          "oldValue": { "Value": 2 },
          "newValue": { "Value": 3 }
        }
      ]
    }
    

    Surfacing Audit History in Model-Driven Apps

    For everyday business users, the right interface for audit data is the record form itself. Model-driven apps expose audit history through a built-in mechanism — no custom development required.

    Enabling the Audit History Tab on a Form

    When auditing is configured on a table, the form editor gives you access to a special "Audit History" section. To add it:

    In the classic form editor: go to the form's Insert tab and choose "Related Data Panel" or use the "Audit History" control. In the modern form editor (Power Apps Studio): open the form, select the component palette, and add the Audit History component to a new or existing tab.

    This renders an embedded subgrid on the form that shows the audit history for the current record. Users with the "View Audit History" privilege in their security role will see this; users without it will see an empty panel or nothing at all.

    Tip

    Don't put the Audit History component on the main form tab — it creates visual clutter for users who check records frequently and don't care about history. Instead, create a dedicated "Audit & Compliance" tab on the form and place it there. This is a simple form design pattern that keeps the primary data-entry experience clean while keeping audit history accessible.

    Security Privileges for Audit Access

    Audit access is governed by two distinct privileges in the security role editor:

    • View Audit History — allows a user to see the audit log for records they have read access to
    • View Audit Summary — allows viewing the full audit log across all records, regardless of record-level access

    The "View Audit Summary" privilege is powerful and should be granted sparingly — typically only to compliance officers, system administrators, and your SOC team. A sales user should be able to see the audit history for their own account records, but not for accounts they don't own.

    These privileges sit in the "Audit" section of the security role editor. They're often overlooked during role design because they're not part of the main entity privilege grid. Make sure your security role design process explicitly evaluates these audit permissions.


    Designing an Audit Strategy for Real Compliance Frameworks

    This is where the lesson moves from configuration to architecture. Different compliance frameworks have different requirements, and a one-size-fits-all "audit everything forever" approach is usually both too expensive and too noisy to be operationally useful.

    GDPR Considerations

    GDPR creates a tension in audit design. On one hand, you need audit trails to demonstrate accountability — proving you followed your stated data handling procedures. On the other hand, audit logs contain personal data, and personal data must be handled according to GDPR's data minimization and right-to-erasure principles.

    This creates a specific problem: if a user exercises their right to erasure (Article 17) and you delete their contact record, audit logs may still contain that person's name, email, and other PII in the changedata JSON. Legally, this is nuanced — audit logs may be retained under the "legitimate interest" or "legal obligation" legal bases, which can override the right to erasure — but you need to document your legal basis explicitly.

    Practically speaking: ensure your data retention and erasure policies address audit logs specifically, and that your legal team has signed off on the retention basis. Do not treat audit logs as an afterthought in your GDPR data map.

    GDPR also requires you to track access to personal data (Article 30 record of processing activities). This is where Dataverse's Access Auditing option becomes important for sensitive tables — enabling it gives you a log of who accessed which contact or patient record, even if they only read it.

    SOX Considerations

    Sarbanes-Oxley focuses on financial controls and the integrity of financial reporting. For organizations using Dataverse to manage financial data (accounts receivable, revenue records, pricing), SOX requires:

    • Change controls: Who changed financial data and when
    • Approval evidence: That changes were authorized (this is where Business Process Flows and approval workflows complement audit logs)
    • Segregation of duties: That the same person can't both enter and approve a transaction

    Dataverse auditing handles the first point natively. For the second and third, you need Business Process Flows and Power Automate approval workflows to generate the approval evidence that lives alongside the audit trail.

    SOX also typically requires extended retention — often 7 years for financial records. Since Dataverse's maximum native retention is typically 1 year (with some licensing providing "forever" retention), SOX-compliant organizations need an archival pipeline. We'll cover this in the storage management section.

    HIPAA Considerations

    HIPAA's Security Rule requires audit controls that "record and examine activity in information systems that contain or use electronic Protected Health Information" (ePHI). For Dataverse environments storing patient data:

    • Enable both change auditing and access auditing on all tables containing ePHI
    • Ensure the "View Audit Summary" privilege is restricted to your HIPAA Security Officer and designated backup
    • Implement real-time alerting for anomalous access patterns (Power Automate + a SIEM, or Microsoft Sentinel)
    • Document your audit log review procedures — HIPAA requires regular review, not just existence of logs

    Warning

    HIPAA is explicit that audit controls must cover access by internal users, not just external threats. The most common HIPAA violation scenario is a curious employee browsing records they have no business reason to access. Dataverse Access Auditing, combined with Power Automate alerting, is your defense here.


    Audit Storage Management and Retention Policies

    Audit logs consume Dataverse database storage. In large environments with aggressive audit configurations, this can become substantial — hundreds of gigabytes over time. Understanding storage mechanics helps you balance compliance coverage with operational cost.

    How Much Storage Will Audit Use?

    Each audit record is roughly 1-4 KB, depending on the number of columns changed and the size of their values. As a rough estimate:

    • A table with 10,000 records updated 3 times per day, with 5 audited columns per update: ~150 MB per month
    • An organization with 20 active tables at similar volumes: ~3 GB per month

    At those rates, a 1-year retention policy for a mid-sized organization generates 30-40 GB of audit data. That's not alarming, but it's not free either — it consumes from your Dataverse database capacity allocation.

    You can check current audit storage consumption from the Power Platform admin center under Environment → Resources → Capacity.

    Archiving Audit Data

    For compliance frameworks requiring retention beyond what Dataverse natively supports, the standard approach is to pipe audit records to an external store before they're purged.

    The architecture looks like this:

    1. A Power Automate scheduled flow runs daily (or on a frequency appropriate to your volume)
    2. The flow queries the audits endpoint for records created in the past 24 hours
    3. The flow writes those records to Azure Blob Storage (as JSON) or to Azure SQL Database (parsed into a structured schema)
    4. An Azure Data Explorer cluster or a Synapse workspace makes the archived data queryable for investigators

    For the Blob Storage path, the flow looks like:

    • Trigger: Recurrence (daily at 2 AM)
    • Action: HTTP (GET /api/data/v9.2/audits?$filter=createdon ge YESTERDAY)
    • Loop through pages
    • Action: Create blob in Azure Blob Storage with filename pattern audit-{date}-{page}.json

    For the SQL path, you'd parse changedata in the flow and insert rows into a structured AuditLog table. The SQL approach makes investigative queries far faster but requires more ETL work and a schema definition.

    Tip

    Even if your compliance framework doesn't require extended retention, archiving audit data to a cheaper storage tier (blob vs. Dataverse database) reduces your Dataverse capacity consumption. Archive after 90 days to blob, and you might save significant money while technically being able to produce 7 years of history if needed.

    Manually Purging Audit Records

    Dataverse allows System Administrators to manually purge audit records. The UI is in the Power Platform admin center: Environment → Settings → Audit and logs → Audit Log Management.

    Here you can delete audit records older than a specified date. This is a destructive, irreversible operation. Before executing a manual purge:

    1. Confirm you've archived the data you're about to delete
    2. Check your compliance framework's retention requirements — purging before minimum retention has been met is a compliance violation, not just a mistake
    3. Verify that the purge action itself will be captured in the audit log (it will — action code 65)

    The fact that purge operations are themselves audited is intentional. If a bad actor with System Administrator access tries to cover their tracks, the deletion of audit records leaves a meta-trail. A sophisticated attacker would need to delete the purge audit records too — which creates yet more audit records, and so on. This self-referential property is a genuine security feature.


    Real-Time Monitoring and Alerting

    Static audit logs answer "what happened?" but they don't prevent damage or trigger response. For a mature compliance posture, you need real-time alerting on high-risk events.

    Power Automate-Based Alerting

    The simplest alerting architecture uses a Dataverse trigger on the audit table. Since audit is a system table and not directly available as a Power Automate trigger entity, you have a few options:

    Option 1: Scheduled polling A flow runs every 15 minutes, queries for new audit records matching specific criteria (action code 64 = auditing disabled, or changes to financial columns above a threshold), and sends alerts.

    Option 2: Webhook via Azure Event Grid Power Platform publishes a Microsoft Dataverse connector event for Record Created on system entities including audit. This triggers a flow in near-real-time. Configure the trigger on the Audit table and filter for high-priority action codes.

    Option 3: Microsoft Sentinel Integration For enterprise SOC environments, Dataverse audit logs can be forwarded to Microsoft Sentinel via the Microsoft Sentinel for Dynamics 365 data connector. Sentinel's KQL-based analytics rules give you industrial-strength pattern detection — for example, alerting when a user accesses more than 100 records in 10 minutes, or when records in a restricted table are accessed outside business hours.

    Here's a simple Power Automate flow outline for credit-limit-change alerting:

    Trigger: When a row is added (Audit table) — Use a scheduled approach
        OR: Recurrence every 15 minutes
    
    Actions:
    1. List rows from Audit where:
       - createdon > utcNow(-15 minutes)
       - objecttypecode = 'account'
       - operation = 2 (Update)
    
    2. For each audit record:
       a. Parse changedata JSON
       b. Condition: Does changedAttributes contain 'creditlimit'?
       c. If yes:
          - Get account name
          - Compose alert message with old/new values, timestamp, user
          - Send Teams notification to Finance Compliance channel
          - Create a record in custom "Compliance Alert" table
    

    Key insight

    The most valuable alerts aren't just about what changed — they're about who changed it and when. A sales manager adjusting credit limits during business hours is routine. The same change made by an IT service account at 2 AM on a Sunday is an anomaly worth investigating immediately.


    Building an Audit Report in Model-Driven Apps

    Beyond individual record history, compliance teams need org-wide audit reports. The right tool for this is a dedicated model-driven view on the audit table, combined with Advanced Find.

    Creating a Custom View for Audit Queries

    The audit table is accessible in the model-driven app experience through the legacy Audit Summary View, but it's limited. For power users, a better approach is to use the Dataverse Advanced Find (or its Fetch XML equivalent) to build saved views.

    Navigate to Advanced Find in your model-driven app (the funnel icon in the top navigation). Select "Audit History" as the entity. You can filter by:

    • Date range
    • Record type (Account, Contact, Opportunity, etc.)
    • User
    • Action type

    Save this as a shared view and give it to your compliance team.

    For more polished reporting, consider building a custom page in your model-driven app using canvas app components. A canvas custom page can query the audit Web API, parse changedata client-side, and present a filterable, formatted compliance report — far more usable than raw audit subgrids.

    An alternative approach for data-heavy audit reporting is to connect Power BI to Dataverse using the Dataverse connector and build a dedicated Audit Dashboard. Power BI can query the audit table, apply transformations with Power Query to parse changedata, and produce visualizations showing change volume by user, table, and time period.


    Hands-On Exercise

    This exercise builds a complete audit configuration for a fictional financial services company's "Loan Application" table. Work through each step in a sandbox environment.

    Scenario

    You're an architect for Contoso Financial. They've built a model-driven app for loan origination using a custom contoso_loanapplication table. The CISO has requested that the following changes be auditable for 2 years, with real-time alerting if a loan amount is changed after initial submission:

    • Loan amount (contoso_loanamount)
    • Applicant credit score (contoso_creditscore)
    • Status reason (statuscode)
    • Assigned underwriter (owner/assigned user)
    • Approval decision (contoso_approvaldecision)

    Step 1: Enable Environment-Level Auditing

    1. Go to admin.powerplatform.microsoft.com
    2. Select your sandbox environment
    3. Navigate to Settings → Audit and logs → Audit settings
    4. Enable "Start Auditing"
    5. Set Retention period to the maximum available (verify with your licensing — if you're on a per-app plan you may be limited; Dynamics 365 licenses often include 1-year+ retention)
    6. Save

    Step 2: Configure Table-Level Auditing

    1. Go to make.powerapps.com → your sandbox environment → Data → Tables
    2. Find or create contoso_loanapplication
    3. Open table properties → Advanced options
    4. Enable "Audit changes to its data"
    5. Save and publish

    Step 3: Configure Column-Level Auditing

    For each of these columns, open the column properties and enable auditing:

    • contoso_loanamount
    • contoso_creditscore
    • statuscode (this is the status reason system column — it's usually available for audit configuration)
    • ownerid (the owner column — this may be enabled by default; verify)
    • contoso_approvaldecision

    Save and publish after each change, or batch your saves.

    Step 4: Verify Audit Capture

    1. Open a loan application record (create a test one if needed)
    2. Change the loan amount and save
    3. On the record form, find the Audit History tab (you may need to add it per the form configuration steps above)
    4. Confirm a new audit entry appeared with the old and new loan amount values
    5. Check that the entry shows your user identity as the modifier

    Step 5: Build an Alert Flow

    1. In Power Automate, create a new flow: Scheduled, every 15 minutes
    2. Add an HTTP action to call: GET /api/data/v9.2/audits?$filter=objecttypecode eq 'contoso_loanapplication' and operation eq 2 and createdon ge TIMESTAMP
      • Replace TIMESTAMP with a dynamic expression: formatDateTime(addMinutes(utcNow(), -15), 'yyyy-MM-ddTHH:mm:ssZ')
    3. Parse the JSON response using the Parse JSON action
    4. Add an Apply to each loop over the audit records
    5. Inside the loop, add a Condition checking if changedata contains contoso_loanamount
    6. If true: Send a Teams message to your compliance channel with the audit record details
    7. Save and test by modifying a loan amount in your app

    Step 6: Validate Security

    1. Create a test user in your environment with a custom security role that has Account Executive permissions
    2. Assign that user "View Audit History" but NOT "View Audit Summary"
    3. Log in as that test user
    4. Open a loan application record they own — confirm they can see audit history for that record
    5. Try to access the global Audit Summary — confirm they cannot

    Common Mistakes & Troubleshooting

    Mistake 1: Enabling Auditing But Not Publishing

    Configuration changes in Dataverse require publishing to take effect. If you enable audit on a table or column but don't publish the customization, the change is stored in your solution but not active in the environment. Always publish after configuring audit settings. In the modern maker experience, this happens automatically on save for some changes but not all — develop the habit of explicitly publishing.

    Mistake 2: Assuming Related Records Are Covered

    Many architects enable auditing on a parent table and assume that auditing cascades to child records. It doesn't. Audit is configured per-table. If you audit the Account table but not the Contact table, changes to contacts related to that account are not captured. Map your entire data model against compliance requirements and configure each table independently. This connects directly to your data model design — the entity-relationship structure determines which tables need audit coverage.

    Mistake 3: Missing the Change in Lookup Values

    When a lookup column changes — for example, a contact's parent account is changed from Contoso to Fabrikam — the audit record captures the GUID of the new and old related records, not their display names. When you later read the audit log, those GUIDs need to be resolved to names for human readability. Build this resolution step into any audit report or alert flow you create. Attempting to audit reports that show raw GUIDs will not satisfy compliance auditors.

    Mistake 4: Forgetting System Processes

    Power Automate flows, plugins, and system jobs make changes to records, and those changes appear in the audit log attributed to the user context of the process — often a service account or application user. If you see a large number of mysterious audit entries from a user named "SYSTEM" or a service account name, that's expected. Document which automated processes run under which identity, so investigators can quickly distinguish human changes from system changes.

    Mistake 5: Not Testing Read Access Before a Compliance Review

    The audit history subgrid on a form is only visible to users with the "View Audit History" privilege. It silently shows nothing (or is hidden entirely) for users without that privilege. Before your external audit, verify with a test user in each relevant role that they can actually see what you expect them to see. An auditor who sits down at a compliance officer's workstation and sees blank panels will lose confidence in your implementation immediately.

    Troubleshooting: Audit Records Not Appearing

    If you make a change to a record and no audit entry appears, check in this order:

    1. Environment auditing on? Go to admin center → Audit settings → confirm "Start Auditing" is enabled
    2. Table auditing enabled? Open table properties → verify "Audit changes to its data" is checked and published
    3. Column auditing enabled? Open the specific column → verify auditing checkbox is enabled
    4. Did you actually change an audited column? If you opened a form and saved without changing any audited fields, no audit record is generated (this is correct behavior)
    5. Is there a licensing issue? Some Dataverse for Teams or free developer licenses have restricted audit capabilities. Verify your environment is provisioned with a license tier that supports auditing

    Tip

    A quick diagnostic: make a change to a column you're confident is audited, then immediately query /api/data/v9.2/audits?$orderby=createdon desc&$top=5 via Postman or browser (with appropriate authentication). If a record appears there but not in the UI, you have a security role / view issue. If no record appears at all, you have a configuration issue.

    Troubleshooting: Storage Quota Alerts

    If you receive capacity alerts related to Dataverse database storage and audit logs are a contributor, your options are:

    1. Reduce the retention period setting — this schedules older logs for cleanup
    2. Manually purge logs older than your minimum retention requirement
    3. Implement the archival pipeline to Azure Blob before purging
    4. Reduce the scope of column-level auditing to only the highest-value fields
    5. Upgrade your storage capacity allocation in the admin center

    Summary & Next Steps

    You now have a complete picture of Dataverse auditing — from flipping the environment switch, through thoughtful column selection, all the way to storage management, compliance framework alignment, and real-time alerting.

    The key architectural insights to carry forward:

    Audit is a three-layer system. Environment → table → column. All three must be active for a field to be captured. Gaps at any layer create gaps in your compliance evidence.

    The audit table is queryable and filterable, but changedata is opaque to server-side filtering. Design your investigative workflows around this constraint — filter by record, user, table, and time range at the API level, and parse change details client-side.

    Compliance frameworks differ in what they require. GDPR cares about access tracking and right-to-erasure tensions. SOX cares about financial data integrity and segregation of duties. HIPAA cares about ePHI access by insiders. Know which framework drives your requirements before you design your audit configuration.

    Storage and retention require active management. Default retention periods are rarely sufficient for enterprise compliance. Build an archival pipeline early, before you need it.

    Audit without alerting is reactive. A mature implementation monitors the audit stream in real time and generates alerts for anomalous changes. Power Automate and Microsoft Sentinel are your primary tools here.

    Where to Go Next

    With auditing in place, your next priorities for a complete compliance posture are column-level security (restricting who can even see sensitive fields, complementing audit trails with preventive controls) — covered in Column-Level Security and Record Sharing in Dataverse. You should also revisit your security role design with fresh eyes: now that you understand what audit captures and who can view it, you may identify gaps in your privilege assignments. The Dataverse Security: Business Units, Security Roles, and Teams lesson provides the framework for that review.

    Finally, if your compliance use cases require immutable evidence — not just audit trails but cryptographically signed records that can survive legal scrutiny — explore Microsoft Purview's integration with Power Platform. Purview extends beyond what Dataverse native auditing provides, offering eDiscovery workflows, legal hold capabilities, and audit log export to formats acceptable in judicial proceedings.

    Your audit system is only as good as the last time you tested it. Put a recurring calendar reminder to validate audit capture monthly — spot-check three records across your most sensitive tables, confirm the history is accurate, and verify your alerting flows are still active. Compliance is a discipline, not a one-time configuration.

    Work With Us

    From insight to implementation

    Reading is the start. When you're ready to build the data, automation, or AI systems behind it, our team turns strategy into shipped results.

    Let's Build

    Model-Driven Apps & Dataverse

    Previous

    Importing and Migrating Data into Dataverse: Excel Import, Dataflows, and Upserts

    Next

    Solutions for Model-Driven Apps: Publishers, Managed vs Unmanaged, and Solution Layering

    Related Insights

    Power AppsExpert

    Designing Dataverse Hierarchical Relationships and Rollup Security: Modeling Parent-Child Table Hierarchies, Hierarchical Security Roles, and Visualizing Tree Structures in Model-Driven Apps

    32 min
    Power AppsPractitioner

    Configuring Dataverse Polymorphic Lookups and Customer Columns: Modeling Multi-Table Relationships in Model-Driven Apps

    24 min
    Power AppsPractitioner

    Configuring Dataverse Virtual Tables: Connecting External Data Sources to Model-Driven Apps Without Migration

    26 min

    On this page

    • Introduction
    • Prerequisites
    • Understanding the Audit Architecture: Three Layers, One Coherent System
    • What Dataverse Actually Captures
    • Configuring Auditing: Step by Step
    • Environment-Level Configuration
    • Table-Level Configuration
    • Column-Level Configuration
    • Audit Data Internals: What's Happening Under the Hood
    • Querying Audit Data with the Web API
    • Basic Audit Query
    • Filtering by Table Type
    • Querying by User
    • Retrieving Audit History Through the SDK Action
    • Parsing changedata for Analysis
    • Surfacing Audit History in Model-Driven Apps
    • Enabling the Audit History Tab on a Form
    • Security Privileges for Audit Access
    • Designing an Audit Strategy for Real Compliance Frameworks
    • GDPR Considerations
    • SOX Considerations
    • HIPAA Considerations
    • Audit Storage Management and Retention Policies
    • How Much Storage Will Audit Use?
    • Archiving Audit Data
    • Manually Purging Audit Records
    • Real-Time Monitoring and Alerting
    • Power Automate-Based Alerting
    • Building an Audit Report in Model-Driven Apps
    • Creating a Custom View for Audit Queries
    • Hands-On Exercise
    • Scenario
    • Step 1: Enable Environment-Level Auditing
    • Step 2: Configure Table-Level Auditing
    • Step 3: Configure Column-Level Auditing
    • Step 4: Verify Audit Capture
    • Step 5: Build an Alert Flow
    • Step 6: Validate Security
    • Common Mistakes & Troubleshooting
    • Mistake 1: Enabling Auditing But Not Publishing
    • Mistake 2: Assuming Related Records Are Covered
    • Mistake 3: Missing the Change in Lookup Values
    • Mistake 4: Forgetting System Processes
    • Mistake 5: Not Testing Read Access Before a Compliance Review
    • Troubleshooting: Audit Records Not Appearing
    • Troubleshooting: Storage Quota Alerts
    • Summary & Next Steps
    • Where to Go Next