Notion CRM to Dataverse Two-Way Sync
Bidirectionally syncs a Notion CRM database with a Dataverse account/contact table: new/changed Notion records upsert into Dataverse and changes in Dataverse write back to Notion, keyed on a shared ID with a source flag to prevent loops. Lets a Notion-first team feed enterprise Power Platform apps and reporting.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
This solution keeps a Notion CRM database in sync with a Dataverse table both ways, loop-safely. New or changed Notion records upsert into Dataverse, and Dataverse-side edits write back to the originating Notion page. A shared cross-key (the Notion page id stored on each Dataverse row) plus a source flag prevent infinite sync loops, so a Notion-first team can feed enterprise Power Apps and Power BI off an authoritative Dataverse copy.
Why it matters: Notion is a friendly CRM front end, but the rest of the business runs on Dataverse. A loop-safe two-way sync gives each side a current, authoritative copy without duplicates.
Ships Off (Stopped). The IP connector has no update-page operation, so Notion write-back uses an HTTP PATCH.
Use Case
A Notion-first sales team wants their CRM reflected in Dataverse so the rest of the business (Power Apps, Power BI) runs off an authoritative copy, while edits on either side stay in sync without duplicates or loops.
Flow Architecture
Flow A: Poll Notion CRM (hourly)
RecurrencePolls the Notion CRM database for current records.
Initialize Correlation & Config
Initialize variableMints a guid() correlation id and binds the Notion DB id, CRM table, source label, and Teams group/channel; seeds the sync counter.
Query Notion CRM
Notion - Query_a_databaseReads all rows from the CRM database.
For Each Notion Record
Apply to each (concurrency 1)Extracts page properties, checks Dataverse for an existing row by Notion page id, then updates or creates the Dataverse row stamped with source=Notion, last-synced, and the correlation id.
Post Sync Summary
Teams - PostMessageToConversationPosts the processed count and correlation id.
Flow B: When CRM Row Modified
Dataverse - SubscribeWebhookTriggerFires on updates to flowlibs_crmcontact (organization scope).
Loop Guard
ConditionIf the row source is Notion, do nothing (that change came from Flow A) - this breaks the loop. Only genuine Dataverse-side edits proceed.
Write Back to Notion
HTTP - PATCH /v1/pagesFor linked rows, writes the edited fields back to the Notion page properties; posts a writeback confirmation to Teams (or a conflict notice when no Notion link exists).
Environment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_NotionCrmDatabaseId | String | REPLACE_WITH_NOTION_CRM_DATABASE_ID | Notion CRM database id (Flow A source). |
| flowlibs_CrmTableName | String | flowlibs_crmcontact | Dataverse table logical name (Flow B trigger entity). |
| flowlibs_SyncSourceLabel | String | Notion | Source flag value - shared loop-guard label across both flows. |
| flowlibs_NotionApiToken | String | REPLACE_WITH_NOTION_INTEGRATION_SECRET | Notion integration secret for the HTTP writeback. |
| flowlibs_NotionApiVersion | String | 2022-06-28 | Notion-Version header for the HTTP writeback. |
| flowlibs_TeamsGroupId | String | <your-team-id> | Teams team id for notifications. |
| flowlibs_TeamsChannelId | String | <your-channel-id> | Teams channel id for notifications. |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| Notion | shared_notionip | Query_a_database |
| Microsoft Dataverse | shared_commondataserviceforapps | ListRecords CreateRecord UpdateRecord SubscribeWebhookTrigger |
| Microsoft Teams | shared_teams | PostMessageToConversation |
| HTTP | shared_http | PATCH /v1/pages |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Field mapping
- The Notion property to Dataverse column mapping is in the Compose actions (Name/Email/Company/Status/Phone). Edit those and the Notion PATCH body to match your CRM schema.
- Field-level direction
- Make some fields one-way by removing them from the opposite flow's write (e.g. Notion owns notes, Dataverse owns credit status).
- Poll window
- Widen Flow A's recurrence for large CRMs; optionally delta-filter on last_edited_time in-flow.
- Conflict policy
- Extend the conflict branch to create the missing Notion page if you want auto-provisioning instead of an alert.
- Delete handling
- Archive rather than hard-delete across systems (use a status/archived flag).
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01Upsert existence check (Flow A)
OData filter to find an existing Dataverse row by Notion page id.
EXPR.02Upsert decision (Flow A)
True when a matching row already exists (update vs create).
EXPR.03Loop guard (Flow B)
Skip rows written by Flow A.
EXPR.04Correlation id propagation (Flow B)
Propagates the trace id across both flows.
EXPR.05Notion property read (Flow A)
Safe read of a Notion title property.
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.