Dual-write is a Microsoft-provided infrastructure that enables near-real-time, bidirectional data synchronization between Dynamics 365 Finance & Operations (F&O) apps and customer engagement (CE) apps built on Microsoft Dataverse. It ships as two Marketplace solutions installed on Dataverse that extend the table schema, plugins, and workflows to handle ERP-scale data volumes. Covers all F&O SKUs (Finance, Supply Chain Management, Commerce, Project Operations). Does NOT cover Business Central integrations.
| Property | Value |
|---|---|
| Vendor | Microsoft |
| System | Dynamics 365 Finance & Operations (10.0.x, continuous updates) |
| API Surface | Dual-write (bidirectional OData-based sync framework) |
| Current Version | Continuously updated via Marketplace solutions |
| Editions Covered | Finance, Supply Chain Management, Commerce, Project Operations |
| Deployment | Cloud (required) |
| API Docs | Dual-write documentation |
| Status | GA (Generally Available) |
Dual-write is a synchronization framework, not a traditional API. It uses table-to-table maps defining how data flows between F&O data entities and Dataverse tables, handling OData and plugin orchestration automatically.
| Capability | Protocol | Best For | Max Records/Request | Timeout | Real-time? | Bidirectional? |
|---|---|---|---|---|---|---|
| Live Sync (F&O to Dataverse) | OData/Plugins | Individual record CRUD, master data | 1,000 records/transaction | 2 min | Yes | Yes |
| Live Sync (Dataverse to F&O) | OData/Plugins | Individual record CRUD, master data | 116.85 MB payload/transaction | 2 min | Yes | Yes |
| Initial Sync (F&O to Dataverse) | Batch export | Bulk data load, go-live migration | 500,000 rows/run/legal entity | 5 min (export) | No | Configurable |
| Initial Sync (Dataverse to F&O) | Batch import | Bulk data load, go-live migration | 500,000 rows/run/legal entity | 24 hours | No | Configurable |
| Catchup Sync | Queue-based | Recovery after pause/maintenance | Queue-based (no explicit limit) | 1 day retention | No | Yes |
| Limit Type | Value | Direction | Notes |
|---|---|---|---|
| Max records per transaction | 1,000 | F&O to Dataverse | Split transactions with >1,000 records into batches of 1,000 |
| Max payload per transaction | 116.85 MB | Dataverse to F&O | Error code -2147220970 if exceeded |
| Transaction timeout | 2 minutes | Both directions | All transactions exceeding 2 min are rolled back on both sides |
| Legal entities (live sync) | 250 | Both | Limit during live synchronization |
| Legal entities (environment linking) | 40 | Setup phase | Error if >40 legal entities during initial linking |
| Concurrent requests | Governed by Dataverse service protection limits | F&O to Dataverse | Per-tenant daily API call limits apply |
| Limit Type | Value | Direction | Notes |
|---|---|---|---|
| Max rows per run | 500,000 | Both | Per legal entity; each legal entity runs separately |
| Single-threaded table max rows | 70,000 | Dataverse to F&O | Timeout if exceeded; migrate data separately instead |
| F&O data export timeout | 5 minutes | F&O to Dataverse | Optimize queries, add missing indexes, check virtual columns |
| F&O import result timeout | 24 hours | Dataverse to F&O | For very large datasets, migrate independently |
| Max lookups per table map | 10 | Dataverse initial sync | Split lookups into batches if >10 |
| Visible errors in error log | 5 | Both | Only top 5 errors shown per initial sync run |
Dual-write does not require separate OAuth or API key authentication. The integration is established by linking F&O and Dataverse environments through the Power Platform integration process.
| Setup Method | Use When | Configuration | Notes |
|---|---|---|---|
| Power Platform Integration (LCS) | New F&O deployments | Link via LCS environment setup | Recommended for new implementations |
| Power Platform Admin Center | Existing environments | Connect existing F&O to existing Dataverse | Requires Global Admin or Dynamics 365 Admin role |
| Dual-write Connection Setup | After environment linking | Configure in F&O Dual-write admin workspace | Only the linking user can create initial maps |
ttsbegin/ttscommit blocks of 1,000 or fewer. [src2]START -- User needs to sync data between F&O and Dataverse/CE apps
|-- Is the data master data (customers, vendors, products)?
| |-- YES --> Dual-write with pre-built table maps
| |-- NO --> Is it transactional data (orders, invoices)?
| |-- YES --> Dual-write (prospect-to-cash, procure-to-pay)
| |-- NO --> Consider Data Management Framework or custom integration
|-- What's the data volume per transaction?
| |-- < 1,000 records --> Standard Dual-write live sync
| |-- 1,000-500,000 records --> Split into transactions of 1,000 records each
| |-- > 500,000 records --> Migrate independently, skip initial sync
|-- Do you need bidirectional sync?
| |-- YES --> Dual-write (design conflict resolution via "Master" setting)
| |-- NO, read-only from F&O --> Consider Virtual Entities (no data duplication)
|-- Is the F&O environment cloud-hosted?
| |-- YES --> Dual-write supported
| |-- NO (on-premise) --> Dual-write NOT supported; use OData or custom integration
|-- Encountering timeouts?
| |-- Transaction timeout (2 min) --> Optimize Dataverse plugins, reduce mapped fields
| |-- Initial sync timeout --> Check data volume against limits
| Category | Example Maps | F&O Entity | Dataverse Table | Threading | Notes |
|---|---|---|---|---|---|
| Customer Master | Customers V3 | CustCustomerV3Entity | account | Single | Max 70K rows from Dataverse |
| Vendor Master | Vendors V2 | VendVendorV2Entity | msdyn_vendor | Single | Max 70K rows from Dataverse |
| Product Master | Released products V2 | EcoResReleasedProductV2Entity | msdyn_sharedproductdetail | Multi | Up to 500K rows |
| Sales | Sales order headers/lines | SalesOrderHeaderV2Entity | salesorder | Multi | Prospect-to-cash |
| Inventory | On-hand inventory | InventOnHandEntity | msdyn_inventoryonhand | Multi | Near-real-time availability |
| Project | Project contracts/tasks | ProjProjectEntity | msdyn_project | Multi | Project Operations |
| Finance | Ledger/Currencies | LedgerEntity | msdyn_ledger | Multi | Prerequisite for all maps |
| Organization | Legal entities | CompanyInfoEntity | cdm_company | Multi | Must be synced first |
Establish the Power Platform integration by connecting your F&O environment to a Dataverse instance via LCS or Power Platform Admin Center. [src1]
Via LCS:
1. Open LCS > Environment details for your F&O environment
2. Click "Power Platform Integration" > "Setup"
3. Select existing Dataverse environment or create new one
4. Confirm linking
Verify: In F&O, navigate to System Administration > Setup > Dual-write -- the admin workspace should appear.
Install the two required solutions on your Dataverse environment from the Dynamics 365 admin center. [src1]
Via Power Platform Admin Center:
1. Navigate to admin.powerplatform.microsoft.com
2. Select your Dataverse environment
3. Go to Resources > Dynamics 365 apps
4. Install "Dual-write core solution" and "Dual-write application solution"
5. Wait 10-30 minutes for installation
Verify: Open Dual-write admin workspace in F&O -- table maps should populate.
Run initial sync following the dependency order: Company > Currency > Customers > Vendors > Products > Transactional data. [src3]
Recommended order:
1. Company and currencies (prerequisites)
2. Chart of accounts and financial dimensions
3. Customers V3 (single-threaded: <70K rows from Dataverse)
4. Vendors V2 (single-threaded: <70K rows from Dataverse)
5. Products (multi-threaded: up to 500K rows)
6. Sales orders and lines
7. Remaining maps
For tables >500K rows: migrate independently + skip initial sync
Verify: Check Initial sync details tab -- status should show Completed.
Set up error thresholds to automatically pause Dual-write during outages. [src4]
In Dual-write admin workspace:
1. Select table map to monitor
2. Click "Create alert settings"
3. Configure: 10 Application errors in 15 minutes > Pause + email
4. Restart table map for alerts to take effect
Verify: Trigger a test error and confirm notification delivery + automatic pause.
// Input: Collection of records to create in F&O (syncs to Dataverse)
// Output: All records created in batches of 1,000 within 2-min timeout
int batchCounter = 0;
boolean commitPending = false;
while select myRecordBuffer
where myRecordBuffer.Status == MyStatus::Pending
{
if (batchCounter == 0)
{
ttsbegin;
commitPending = true;
}
myRecordBuffer.insert(); // Triggers Dual-write sync
batchCounter++;
if (batchCounter >= 1000)
{
ttscommit;
commitPending = false;
batchCounter = 0;
}
}
if (commitPending)
{
ttscommit; // Commit remaining records
}
# Input: Dataverse environment URL + access token
# Output: List of Dual-write table maps and status
$envUrl = "https://yourorg.crm.dynamics.com"
$token = "Bearer <your-access-token>"
$response = Invoke-RestMethod `
-Uri "$envUrl/api/data/v9.2/dualwriteentitymaps" `
-Headers @{ "Authorization" = $token; "Accept" = "application/json" } `
-Method Get
$response.value | ForEach-Object {
[PSCustomObject]@{
Name = $_.msdyn_name
Status = $_.msdyn_status
LastSync = $_.msdyn_lastsynctime
ErrorCount = $_.msdyn_errorcount
}
} | Format-Table -AutoSize
| F&O Entity | Dataverse Table | Direction | Key Fields | Threading |
|---|---|---|---|---|
| CustCustomerV3Entity | account (Customers V3) | Bidirectional | CustomerAccount | Single-threaded |
| VendVendorV2Entity | msdyn_vendor (Vendors V2) | Bidirectional | VendorAccountNumber | Single-threaded |
| EcoResReleasedProductV2Entity | msdyn_sharedproductdetail | Bidirectional | ProductNumber | Multi-threaded |
| SalesOrderHeaderV2Entity | salesorder | Bidirectional | SalesOrderNumber | Multi-threaded |
| CompanyInfoEntity | cdm_company | Bidirectional | DataAreaId | Prerequisite |
| CurrencyEntity | transactioncurrency | F&O to Dataverse | CurrencyCode | Prerequisite |
| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| DIPV1000 | Connection validation failed | Power Apps ApiHub connections expired | Re-create connection sets |
| DIPV1002 | Cross-company data sharing conflict | Cross-company sharing enabled on F&O entity | Disable cross-company sharing or use different entity |
| DIPV1003 | Integration keys missing | No alternate key on Dataverse table | Define alternate key in Power Apps maker portal |
| DIPV1010 | Mandatory field unmapped | Required field not mapped in table map | Map all mandatory fields or add default transforms |
| DIPV1019 | Missing versionnumber field | Unsupported table type (virtual/elastic) | Use standard table with versionnumber field |
| -2147220970 | Message size exceeded | Payload >116.85 MB from Dataverse | Split transaction into smaller batches |
| MalformedLineException | Double quote parsing failure | Double quotes in source data | Remove double quotes before syncing |
Move heavy plugins to async; reduce mapped field count. [src2]Add missing indexes; update to PU37+. [src3]Monitor daily; retry or export before 24-hour purge. [src4]Link only needed entities; 250 limit applies post-setup. [src3]Stop in reverse dependency order; restart: Company > Currency > Customers. [src3, src7]Review catch-up errors tab; retry after fixing validation issues. [src4]// BAD -- Each migrated record triggers individual sync, destroying performance
1. Enable Dual-write map for Customers V3
2. Start DMF import of 200,000 customers
3. Each insert triggers sync -- 200K individual operations
4. Result: hours instead of minutes; possible timeouts
// GOOD -- Migrate independently, then enable live sync
1. Import 200,000 customers into F&O via DMF
2. Import matching 200,000 accounts into Dataverse
3. Enable Dual-write maps with "Skip initial sync"
4. Result: migration in minutes; Dual-write handles only new changes
// BAD -- Exceeds Dual-write 1,000-record limit
ttsbegin;
while select forUpdate myRecord where myRecord.Status == Status::New
{
myRecord.Status = Status::Active;
myRecord.update(); // All 5,000 updates in ONE transaction
}
ttscommit; // FAILS: Dual-write rejects >1,000 records
// GOOD -- Split into safe batches
int counter = 0;
ttsbegin;
while select forUpdate myRecord where myRecord.Status == Status::New
{
myRecord.Status = Status::Active;
myRecord.update();
counter++;
if (counter >= 1000) { ttscommit; ttsbegin; counter = 0; }
}
ttscommit;
// BAD -- Shared address tables cause lock contention
1. Customer V3 map: Running (live sync active)
2. Start initial sync for Contacts
3. Result: performance degradation, address table locks, errors
// GOOD -- Stop conflicting maps first
1. Pause Customer V3 map
2. Run Contact initial sync to completion
3. Resume Customer V3 map (catchup handles queued changes)
Follow dependency order: Company > Currency > Customers > Vendors > Products. [src3]Load-test with production-equivalent data volumes. [src7]Migrate single-threaded tables independently; skip initial sync. [src3]Assign both roles to all CE users in Dual-write processes. [src3]Convert to async; optimize to <500ms. [src2, src7]Configure alert notifications in admin workspace; set email alerts. [src4]# Check Dual-write map status in F&O
# Navigate: System Administration > Setup > Dual-write > Table maps
# Check initial sync progress
# Dual-write admin workspace: select map > "Initial sync details" tab
# View catch-up errors after resume
# Dual-write admin workspace: select map > "Catch-up errors" tab
# Check Dataverse API limits usage
# Power Platform Admin Center > Environments > Analytics > API calls
# Test environment connectivity
# Dual-write admin workspace: "Test connection" button
# View activity log for a table map
# Dual-write admin workspace: select map > "Activity log" tab
# Download detailed error logs
# Activity log: click event > "Download logs"
| Release | Date | Status | Key Changes | Notes |
|---|---|---|---|---|
| Continuous updates (2026) | Ongoing | Current | MCP server for Dataverse (GA) | Per-update release cadence |
| 2024 Wave 2 | 2024-10 | Supported | Legal entity limit: 40 to 250 (live sync) | Major scalability improvement |
| 2021 Wave 1 | 2021-04 | Supported | Catchup write management; alert notifications | First error queue UI |
| PU37+ | 2020-11 | Minimum required | 5-min export timeout handling | Required for reliable initial sync |
| Original GA | 2019 | Legacy | Initial Dual-write release | No longer recommended |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Bidirectional near-real-time master data sync between F&O and CE apps | Read-only access to F&O data (no duplication needed) | Virtual Entities |
| Pre-built scenarios: prospect-to-cash, procure-to-pay, project-to-cash | Bulk migration >500K rows per legal entity | Data Management Framework |
| No-code/low-code integration between Microsoft D365 apps | Integration with non-Microsoft systems (SAP, Salesforce) | OData API or Azure Integration Services |
| Cloud-hosted F&O environment | On-premise F&O deployment | OData endpoints or custom API |
| Transaction volumes <1,000 records per operation | High-frequency, >1,000 records/second | Batch processing or event-driven architecture |
| Capability | Dual-write | Virtual Entities | DMF | OData API |
|---|---|---|---|---|
| Data Direction | Bidirectional | Read-only from F&O | Import/Export | Full CRUD |
| Data Storage | Copied to both systems | Data stays in F&O only | Staging tables | Per-request |
| Latency | Near-real-time (<1s) | Real-time (query-time) | Batch (min to hrs) | Real-time |
| Max Records/Op | 1,000/transaction | No limit (query-based) | Millions (file-based) | Standard OData limits |
| Storage Cost | Double (data in both) | None | Staging only | None |
| Setup Complexity | Low (pre-built maps) | Low (configuration) | Medium (packages) | High (custom dev) |
| Offline Support | Yes (catchup on resume) | No | Yes (file-based) | No |
| Custom Table Support | Yes (extensible) | Yes (configurable) | Yes | Yes |
| Best Use Case | Master data sync | Reporting, lookups | Bulk migration, ETL | Custom integrations |