Best Practices for Multi-Company Data Synchronization in Dynamics 365 Business Central

Mark Mckinney

Member
Joined
May 19, 2026
Messages
1
What are the most effective and upgrade-safe approaches for implementing real-time or near real-time data synchronization between multiple companies within Dynamics 365 Business Central, especially for shared master data such as customers, vendors, items, and dimensions?

I’m looking for guidance on:

Using APIs vs. integration tables vs. Dataverse
Handling transaction conflicts and duplicate records
Performance considerations for large datasets
Automating synchronization through Job Queues or background sessions
Maintaining security and company-specific business rules
Recommended AL development patterns for extensibility
 

Hi Mark — for upgrade-safe multi-company synchronization in Dynamics 365 Business Central, I’d avoid “hard” database-style replication and design it as an integration pattern with clear ownership, delta tracking, retry handling, and auditability.

Short recommendation​

For shared master data such as customers, vendors, items, dimensions, payment terms, posting groups, and units of measure, the best long-term patterns are:
ScenarioRecommended approach
Multiple BC companies in same tenantAL extension with an outbox/inbox sync pattern, processed by Job Queue
BC plus Power Platform / Sales / other Dynamics appsDataverse integration
External systems or cross-environment syncBusiness Central APIs / custom API pages
Read-only shared data exposureAPI queries, Dataverse virtual tables, or external reporting layer
True user-facing “single source of truth”Central master-data company or Dataverse hub
Do not directly modify Microsoft base objects or rely on unsupported SQL/database replication. Keep the solution in AL extensions, APIs, events, and Job Queues.

1. APIs vs. integration tables vs. Dataverse​

Option A — Business Central APIs​

Use this when the sync boundary is outside the current company/session, across environments, or when you want a clean service interface.
Good for:
  • Cross-environment synchronization.
  • Integration with external systems.
  • Upgrade-safe contracts.
  • OAuth/service-to-service authentication.
  • Clear separation of business logic.
Recommended patterns:
  1. Use standard APIs where possible.
  2. Create custom API pages for custom fields or entities.
  3. Use SystemId as the API key where possible.
  4. Avoid exposing UI pages as OData services for high-volume sync.
  5. Use $filter, $select, $top, and paging for deltas.
  6. Use retry logic for 429, 503, and 504 responses.
Best when:
  • You want loose coupling.
  • You need environment-to-environment sync.
  • You want integration users and API permissions to control access.

Option B — AL-based company-to-company sync​

Use this when all companies are inside the same BC environment and you need tight control over company-specific rules.
Good for:
  • Shared master data inside one tenant.
  • Company A → Company B/C/D propagation.
  • Controlled synchronization from a “master company.”
  • Custom validation and transformation logic.
Typical AL approach:
  1. Subscribe to table events on Customer, Vendor, Item, Dimension, etc.
  2. Write a row to a custom Sync Outbox table.
  3. Let a Job Queue process the outbox asynchronously.
  4. Use ChangeCompany() carefully when writing to target companies.
  5. Store sync status, retries, errors, timestamps, and source company.
Important warning: ChangeCompany() respects permissions, but table triggers still execute in the current company context. That means you must be very careful with business logic that depends on CompanyName, setup tables, number series, posting groups, or dimensions.
Best when:
  • Sync is mostly internal to BC.
  • You need full AL control.
  • You want near real-time but not blocking user transactions.

Option C — Dataverse​

Use Dataverse when you need a central hub for shared master data, Power Platform, Sales, Customer Service, or multi-app governance.
Good for:
  • Centralized customer/account master.
  • Integration with Dynamics 365 Sales.
  • Power Automate workflows.
  • Multi-environment or multi-company coupling.
  • Match-based coupling and controlled synchronization.
Limitations:
  • Dataverse sync is usually near real-time, not guaranteed instant.
  • It depends on mappings, couplings, filters, and Job Queue processing.
  • You must carefully manage company IDs and filters in multi-company scenarios.
Best when:
  • Dataverse is already part of the architecture.
  • Users outside BC also maintain master data.
  • You need governance, approval, and Power Platform integration.

2. Recommended architecture for multi-company BC sync​

I’d use this pattern:

Procedure: Build an upgrade-safe sync framework​

  1. Define the source of truth.
    Decide whether master data is owned by one BC company, each company, Dataverse, or an external MDM system.
  2. Split global fields from local fields.
    For example, customer name and VAT number may be global, but posting groups, dimensions, credit limits, tax areas, and payment methods may be company-specific.
  3. Add a stable global identifier.
    Add a custom field such as Global Master Data Id: Guid to shared master tables.
  4. Create a mapping table.
    Store source company, target company, source SystemId, target SystemId, entity type, sync status, and last sync timestamp.
  5. Create an outbox table.
    Store pending sync work instead of syncing immediately inside table triggers.
  6. Subscribe to table events.
    Use events such as insert, modify, rename, and delete events to enqueue work.
  7. Process outbox rows with Job Queue.
    The Job Queue should process records in small batches and log success or failure.
  8. Use idempotent upsert logic.
    Running the same sync message twice should not create duplicates or corrupt data.
  9. Prevent sync loops.
    Mark changes made by the sync engine so that target-company updates do not re-enqueue endless reverse changes.
  10. Add telemetry and admin pages.
    Provide pages for pending records, failed records, retry count, last error, and manual reprocessing.

3. Handling conflicts and duplicate records​

This is where many sync projects fail. Do not rely only on No..
Recommended approach:
  1. Use a global GUID for each shared master record.
  2. Keep local No. values if companies have different number series.
  3. Store cross-company mapping in a custom table.
  4. Use match rules during initial coupling.
  5. Use deterministic conflict resolution.
Good matching fields:
  • Customer: VAT Registration No., registration number, normalized name, email, phone.
  • Vendor: VAT Registration No., bank account, normalized name.
  • Item: GTIN, vendor item no., manufacturer code, item reference.
  • Dimension: code plus dimension type/global GUID.
  • Contacts: email plus company/contact relationship.
Conflict strategies:
Conflict typeRecommended handling
Same record changed in two companiesUse source-of-truth rules or field-level ownership
Duplicate candidate foundPut into review queue, do not auto-merge blindly
Target record missingCreate only if match rules pass
Target record blockedSkip and log error
Local-only field changedDo not overwrite from master
Delete from sourceUsually block/delete-mark instead of hard delete
For master data, I usually recommend field ownership rather than “last write wins.”
Example:
FieldOwner
NameMaster company
AddressMaster company or Dataverse
Customer Posting GroupLocal company
Gen. Bus. Posting GroupLocal company
Payment TermsMaster or local, depending on policy
Default DimensionsOften local
BlockedUsually local

4. Performance considerations for large datasets​

For large data volumes, design for delta sync, not full sync.
Recommended patterns:
  1. Process only changed records.
  2. Store a last processed timestamp/version/hash.
  3. Use SetLoadFields() when reading only selected fields.
  4. Use FindSet() with filters and limited batch sizes.
  5. Avoid heavy logic in page triggers or API pages.
  6. Avoid synchronous cross-company writes inside user transactions.
  7. Process in batches, for example 100–500 records per run.
  8. Commit at safe boundaries, not after every field change.
  9. Use separate Job Queue categories per entity if needed.
  10. Monitor locks, deadlocks, and long-running sessions.
For APIs:
  1. Prefer API pages/API queries over exposed UI pages.
  2. Use $select to limit columns.
  3. Use $filter for changed records.
  4. Use $top and paging.
  5. Use $batch where appropriate.
  6. Handle throttling with exponential backoff.
  7. Do not run massive full syncs during business hours.
For initial load:
  1. Take a backup or test in sandbox first.
  2. Run matching/coupling before insert.
  3. Import in entity dependency order.
  4. Start with dimensions, posting groups, payment terms, units of measure.
  5. Then sync items, vendors, customers.
  6. Then enable incremental sync.

5. Job Queues and background sessions​

For near real-time sync, I recommend event-driven enqueue + scheduled background processing.

Better pattern​

  1. User modifies a customer.
  2. Event subscriber detects relevant change.
  3. Subscriber writes one row to Sync Outbox.
  4. Job Queue runs every 1–5 minutes.
  5. Job Queue processes pending rows.
  6. Errors are logged and retried.
This avoids blocking the user session and makes failures recoverable.

Avoid this pattern​

Do not do heavy cross-company synchronization directly inside OnAfterModify or OnAfterInsert.
Problems with direct sync:
  • Slows down user posting/data entry.
  • Increases lock duration.
  • Makes conflicts harder to recover.
  • Can create recursive sync loops.
  • Can fail the user’s transaction because another company has invalid setup.

Job Queue design​

Use separate processors:
  • Customer Sync Processor
  • Vendor Sync Processor
  • Item Sync Processor
  • Dimension Sync Processor
Or one generic processor with parameters:
Code:
codeunit 50100 "Master Data Sync Processor"
{
    TableNo = "Job Queue Entry";

    trigger OnRun()
    begin
        case Rec."Parameter String" of
            'CUSTOMER':
                ProcessCustomers();
            'VENDOR':
                ProcessVendors();
            'ITEM':
                ProcessItems();
            'DIMENSION':
                ProcessDimensions();
        end;
    end;
}
Expected outcome:
  • Users are not blocked by sync work.
  • Failed records can be retried.
  • Admins can review and correct issues.
  • Sync can be paused safely.

6. Security and business rules​

Security must be designed intentionally.
Recommended security model:
  1. Use a dedicated sync user or service principal.
  2. Give only required permissions.
  3. Use permission sets specific to sync objects and target tables.
  4. Respect company access restrictions.
  5. Log the original user who caused the change.
  6. Never bypass validation unless you fully understand the consequences.
  7. Keep company-specific fields protected from global overwrites.
Business rule handling:
  1. Use Validate() for fields where Business Central business logic must run.
  2. Use direct assignment only for technical fields where validation is not needed.
  3. Do not copy posting setup blindly across companies.
  4. Do not assume number series are identical.
  5. Do not assume dimensions are identical unless governed centrally.
  6. Treat blocked records, tax setup, currencies, and posting groups as local unless agreed otherwise.
Important: If you use ChangeCompany(), test permissions and trigger behavior carefully. A sync user must have rights in every target company.

7. Recommended AL development patterns​

Use these patterns for extensibility and upgrade safety.

Use an outbox table​

Example fields:
Code:
Entry No.
Entity Type
Source Company
Target Company
Source SystemId
Target SystemId
Operation Type
Status
Retry Count
Last Error
Created DateTime
Processed DateTime
Correlation Id
Payload Hash

Use a sync context/suppression flag​

Prevent recursive sync loops.
Example concept:
Code:
codeunit 50101 "Sync Context"
{
    SingleInstance = true;

    var
        IsSyncRunning: Boolean;

    procedure SetSyncRunning(Value: Boolean)
    begin
        IsSyncRunning := Value;
    end;

    procedure GetSyncRunning(): Boolean
    begin
        exit(IsSyncRunning);
    end;
}
Then in subscribers:
Code:
[EventSubscriber(ObjectType::Table, Database::Customer, 'OnAfterModifyEvent', '', false, false)]
local procedure CustomerOnAfterModify(var Rec: Record Customer; var xRec: Record Customer; RunTrigger: Boolean)
var
    SyncContext: Codeunit "Sync Context";
begin
    if SyncContext.GetSyncRunning() then
        exit;

    if not RelevantCustomerFieldsChanged(Rec, xRec) then
        exit;

    EnqueueCustomerSync(Rec);
end;

Use idempotent upsert​

Pseudo-flow:
Code:
Find target by Global Master Data Id
If found, update allowed fields
If not found, try match-based coupling
If still not found, create new record
If multiple matches, mark conflict

Use field-level mapping​

Do not copy every field.
Code:
Customer.Name -> sync
Customer.Address -> sync
Customer.Customer Posting Group -> local only
Customer.Gen. Bus. Posting Group -> local only
Customer.Blocked -> local or controlled by policy

Use integration events in your own app​

Expose events such as:
Code:
[IntegrationEvent(false, false)]
local procedure OnBeforeApplyCustomerSync(var Customer: Record Customer; SourceCompany: Text; var IsHandled: Boolean)
begin
end;

[IntegrationEvent(false, false)]
local procedure OnAfterApplyCustomerSync(Customer: Record Customer; SourceCompany: Text)
begin
end;
This lets future extensions add rules without modifying your sync engine.

8. Entity-specific guidance​

Customers​

Recommended:
  • Sync name, address, phone, email, VAT registration no.
  • Keep posting groups and dimensions local unless standardized.
  • Use global ID plus VAT/tax registration matching.
  • Handle customer templates carefully.

Vendors​

Recommended:
  • Sync name, address, VAT registration no., contact info.
  • Treat bank accounts as sensitive and permission-controlled.
  • Keep posting groups, payment methods, and dimensions local unless governed.

Items​

Recommended:
  • Sync description, base unit of measure, GTIN, item category, tracking policy if standardized.
  • Be careful with costing method, inventory posting groups, tax groups, and replenishment settings.
  • Do not sync inventory quantities as master data.

Dimensions​

Recommended:
  • Centralize dimension definitions first.
  • Sync dimension values before syncing default dimensions.
  • Decide whether default dimensions are global or company-specific.
  • Avoid deleting dimension values that may be used in posted entries.

9. Practical “best” architecture​

For most multi-company Business Central projects, I’d implement this:
  1. Master data company owns shared records.
  2. Custom AL extension adds global IDs and sync tables.
  3. Event subscribers enqueue changes.
  4. Job Queue processes changes every few minutes.
  5. Field mapping setup controls which fields sync.
  6. Conflict queue handles duplicates and ambiguous matches.
  7. Per-company setup controls target companies and local rules.
  8. Telemetry and admin pages show health, errors, and retry status.
  9. Dataverse is used only if Power Platform or cross-app MDM is part of the requirement.
  10. APIs are used for cross-environment or external integrations.

10. What I would avoid​

Avoid these if you want the solution to remain upgrade-safe:
  • Direct SQL replication.
  • Modifying base application objects.
  • Copying complete records blindly between companies.
  • Synchronous sync inside table trigger logic.
  • Using only No. as the cross-company identity.
  • Hard deleting master data across companies.
  • Treating posting setup and dimensions as universally identical.
  • Exposing UI pages as high-volume OData endpoints.
  • Running full sync repeatedly instead of delta sync.
  • Ignoring conflict handling until after go-live.

Final recommendation​

For your scenario, the most robust solution is:
Use an AL extension with an outbox/inbox pattern, global record IDs, field-level ownership, Job Queue processing, and conflict management. Use Dataverse if you need a central enterprise data hub or Power Platform integration. Use APIs when synchronization crosses environments, tenants, or external systems.
This gives you:
  • Upgrade safety.
  • Auditability.
  • Retry handling.
  • Better performance.
  • Clear business-rule boundaries.
  • Reduced risk of duplicate or corrupted master data.
 

Back
Top