Recently, one of the users reported the following error while trying to generate a PDF for a Quote record in Dynamics 365:
Initially, the Export to PDF option was showing a blank list of templates.
This happened because the user was missing a few essential privileges on the Document Template tables.
To fix the blank template list, we updated the user’s custom security role with the appropriate privileges on the following tables:
Document Template
Personal Document Template
After adding these, the templates started appearing in the “Export to PDF” dialog.
Even though the templates were now visible, the user still got the following error while trying to preview or export:
This was due to one missing privilege in the Customization area of the security role.
We added: DocumentGeneration privilege
Once this privilege was granted, the preview and PDF generation started working as expected.
If we are unsure which privilege might be missing in similar situations, a quick way to find out is by using Developer Tools (F12) and monitoring the Network tab while reproducing the error. The failed request, such as ExportPdfDocument, usually reveals the missing privilege directly in its Response section (for example, missing prvDocumentGeneration privilege). This saves time and avoids trial and error when troubleshooting permission issues.
Few weeks back, while working on one of our Power Automate flows, we noticed a banner warning on the HTTP Request trigger step. Microsoft has announced that starting August 2025, all flows using HTTP or Teams Webhook triggers with logic.azure.com URLs will move to a new endpoint under environment.api.powerplatform.com. The old URLs will stop working after November 30, 2025.
The screenshot below shows the banner that appears when the flow is opened in the designer.
When we saw this, we wanted to make sure no other flow across environments was using the old logic.azure.com–based URLs. Instead of checking each flow manually, we used SQL4CDS to quickly identify all such flows.
We ran the following query in SQL4CDS:
SELECT wf.name, wf.workflowid, wf.owneridname, wf.modifiedon
FROM workflow wf
WHERE wf.category = 5
AND LOWER(wf.clientdata) LIKE '%"type":"request","kind":"http"%'
This query returns all flows that have an HTTP Request trigger. It checks the clientdata column in the workflow table where the flow definition is stored as JSON and looks for the trigger type “Request” and kind “Http”.
This helped us identify every flow that exposes an HTTP endpoint — typically used for integrations, webhooks, or form submissions from external systems. Once we had this list, we opened each flow and copied the new trigger URL from the message banner.
The same can be achieved using PowerShell with the command: This lists all flows in a given environment that are part of Microsoft’s trigger URL migration.
After copying the new URL, we updated our calling applications (for example, the website forms and marketing integrations) to replace the old endpoint with the new one. The new URLs are hosted under the Power Platform domain environment.api.powerplatform.com, replacing the older logic.azure.com endpoints.
To confirm that the requests were now reaching the new infrastructure, we captured the request headers after updating the URL.
This header (x-ms-igw-external-uri) is the easiest and most reliable way to confirm that your flow is now routed through the new Power Platform ingress gateway.
If this header is present and points to environment.api.powerplatform.com, the flow has been successfully migrated. If you still see logic.azure.com under the Host or DISGUISED-HOST headers, that flow or calling application still needs to be updated.
In our case, after replacing the URLs and testing, all flows were confirmed to be running on the new platform, and the integrations worked as expected.
So if we see this warning in your environment, you can either use the SQL4CDS query or the PowerShell command to locate such flows, update the calling systems with the new URL, and then verify by checking for the x-ms-igw-external-uri header in the request. That’s all you need to ensure your integrations continue to work smoothly past November 2025.
In this post, we’ll look at how we used the ParentContext property in Dynamics 365 plugins to determine if a plugin execution was triggered by another plugin and perform logic conditionally. This came up in two real-world cases — one where we needed to prevent duplicate Sales Leads created automatically through Event Management, and another where we wanted to match the correct Campaign when a Lead was updated through a Marketing Form submission.
In the first scenario, we had a plugin registered on the Pre-Create of Lead. We wanted to block duplicate Sales Leads only when they were created via CRM’s Event Plugin, not during manual Lead creation. To achieve this, we checked if the plugin execution had a ParentContext. When present, it meant the Lead creation was triggered by another process, not a user. We confirmed it was the system’s Event Plugin by checking the msevtmgt_originatingeventid field (this field will be auto-populated for a lead created by event) and depth. If the Lead was of Enquiry Type “Sales” and had an email address, we checked for duplicates and stopped the creation if one existed. This ensured duplicates were blocked only for system-triggered Leads.
The second case involved the plugin, registered on the Update of Lead. Here, we needed to identify if a Lead update was triggered by a Marketing Form submission (from the msdynmkt_marketingformsubmission table) and only then run our Campaign mapping logic. We used ParentContext to walk up the plugin chain and confirm the origin. Once verified, we called our logic to assign the correct Campaign based on the Region or Village. This made sure the Campaign logic only ran for Leads updated by Marketing automation, not for regular user edits.
In both cases, using ParentContext gave us precise control over when the plugin logic should run. It allowed us to differentiate between user actions and system-triggered updates, avoiding redundant execution and maintaining a cleaner automation flow.
Recently, while working with Quotes in Dynamics 365 Sales integrated with Supply Chain Management (SCM) through Dual-write, we encountered an interesting error while trying to activate an existing quote.
When attempting to activate Quote, the system threw the following error message:
Checking the Plugin Trace Log, we found the following details:
Validate calling user $2953f4a9-ffca-ea11-a812-000d3a6aa8ae.
Calling user not Dual-write.
SCM plugin exception: Action cannot be performed. This quote is not owned by Dynamics 365 Sales., at Microsoft.Dynamics.SCMExtended.Plugins.Services.QuoteService.ValidateIntegrationOwnerOnStateCodeChange(LocalPluginContext localContext, Guid quoteId)
Interestingly, this issue occurred only for old quote records — the ones created before Dual-write was enabled.
All newly created quotes after enabling Dual-write worked perfectly fine and could be activated without any error.
When comparing both sets of records, we noticed one key difference:
The msdyn_quotationownership (Ownership) field was blank for old quotes, while it was populated for the new ones.
This field plays an important role once Dual-write is enabled. The Microsoft.Dynamics.SCMExtended.Plugins.QuotePreUpdate plugin checks the Ownership field during operations like quote activation to validate the integration source.
If this field is empty, the plugin assumes the quote doesn’t belong to Dynamics 365 Sales and blocks the action, resulting in the error we saw.
Here we simply needed to set the missing ownership field.
To resolve the issue, we bulk updated all old quotes to set the missing Ownership (msdyn_quotationownership) field to Dynamics 365 Sales.
Once updated, the system immediately allowed us to activate quotes successfully — no more errors.
In Dynamics 365, attribute maps define how data flows from one record to another when creating related records. For example, when creating a Contact from an Account, fields like Address, Phone Number, and Website are copied automatically through predefined mappings. These mappings are part of standard Microsoft solutions like Sales, Customer Service, and Marketing.
If we try deleting these out-of-the-box (OOB) mappings, we will encounter an error — the platform restricts the deletion of system mappings to protect standard data flow and solution integrity.
Using the Block Deletion of Out-of-the-box Attribute Maps feature setting, we can govern this behavior. Navigate to Environment → Settings → Features to locate this option.
When we disable this setting, the platform allows the deletion of OOB mappings.
In some scenarios, it could be applicable, like during lead conversion, where we may want to remove OOB mappings (like Address, Job Title, or Description) to populate these fields using external data. Some organizations use a custom quoting process instead of the OOB Opportunity → Quote flow. They remove the standard attribute maps that automatically copy data into Quote fields to prevent conflicts with their own automation.
However, even if these mappings are deleted, they can reappear when Microsoft pushes future solution updates. The recommendation is to only disable this setting if there is a specific and controlled use case, typically in a non-production environment, and with a backup plan in place. Keeping this setting enabled ensures consistency and prevents loss of important system logic that underpins Dynamics 365’s standard data flow.
Recently, we observed that one of our flows was getting triggered multiple times in our UAT environment; however, the flow was working properly in our Development environment.
On comparing the flows trigger, we didn’t find any differences.
However, when checking for the callbackregistration record, we observed that for the Dev env, we had the callbackregistration record available.
However, it was missing for our UAT environment.
Turning the flow on and off didn’t create the corresponding callbackregistration record.
Eventually, we deleted the trigger and recreated it in the UAT.
After recreating the trigger, we could see our flow getting triggered only once as expected.
However, we also noticed that the name of the callbackregistration record was not just the GUID, but it also had MTA suffixed to it in our UAT.
daf9fae3-a405-ee11-8f6e-00224817f864:MTA
So may this record was already existing and had an incorrect filter expression, which got fixed when we deleted and created a new trigger.
We also deleted this callbackregistration record, and turned our flow on and off. This created a new callbackregistration record with the same MTA suffixed to it.
So the solution here could be to find the callbackregistration record either with a GUID or with MTA suffixed to it, delete the record found, and turn the flow on and off.