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.
Recently, we faced an interesting import failure while moving a solution containing a Custom API.
Solution “Temp Plugin Step Custom API Transfer” failed to import: Lookup value 8f3269b7-a24d-43e4-9319-0c5e7ddf2b53 is not resolvable.
This clearly pointed to a lookup resolution issue — the solution import process was trying to bind the Custom API to a Plugin Type (class), but couldn’t find the referenced plugin in the target environment.
Each Custom API will have its own folder inside the Solution.
Looking into the solution files (specifically the customapi.xml) of that particular Custom API, we found this section:
Notice the <plugintypeexportkey> tag. This is where the Custom API references the Plugin Type (the actual C# class implementing the logic).
When a Plugin class is created in Dynamics 365, it gets assigned a unique Plugin Type Id (GUID).
In the source environment, the Custom API was tied to a plugin with ID 420c7261-7461-4b37-87f0-1afcec427a46. However, in the destination environment, which was another development environment, a different plugin class was already created for that custom api. So during solution import, Dataverse tried to match the GUID 420c7261… but couldn’t find it in the target environment. Hence, the lookup resolution failed, and the solution import was blocked.
To resolve this, we manually updated the GUID in the customapi.xml to match the Plugin Type Id of the destination environment. Below, we are getting the ID from the Plugin Registration tool. The other option to fix would have been to remove the Plugin reference from the source, export, and then import.
After making this change, we re-imported the solution, and it worked successfully.
When working with forms in Dynamics 365 / Power Apps model-driven apps, we often customize field labels based on context, using the setLabel method. At times, we would also like to change the tool tip to go with the changed label of the field. The tooltip is defined as a Description of the field.
Below is the Topic (subject) field of the lead.
However, we cannot set the tool tip (description) of the field dynamically in the form using the Client API. So, what do we do when the meaning of a field changes depending on another value on the form? That’s where addNotification comes in as a handy workaround.
Let us take a simple example to see how we can use it. On the Lead form, the Topic(subject) field means different things depending on the Lead Source. So here we will be changing the label of the Topic (subject) field, along with setting a different notification message.
For e.g., if Lead Source – Advertisement, we are changing the label to Campaign Name. We can also notice the bulb icon next to the field.
Clicking on it, we can see our message –
Similarly, on changing the Lead Source to Web, we are changing the label to Landing Page, and clicking on the icon, we can see a different message.
Sample Code –
function updateSubjectField(executionContext) {
var formContext = executionContext.getFormContext();
var leadSourceAttr = formContext.getAttribute("leadsourcecode");
var subjectControl = formContext.getControl("subject");
subjectControl.clearNotification("subjectTooltip");
var leadSource = leadSourceAttr ? leadSourceAttr.getValue() : null;
if (leadSource === 1) {
// 1 = Advertisement
subjectControl.setLabel("Campaign Name");
subjectControl.addNotification({
messages: ["Enter the name of the ad campaign"],
notificationLevel: "RECOMMENDATION",
uniqueId: "subjectTooltip"
});
}
else if (leadSource === 2) {
// 2 = Referral
subjectControl.setLabel("Referrer Notes");
subjectControl.addNotification({
messages: ["Mention details about the referrer"],
notificationLevel: "RECOMMENDATION",
uniqueId: "subjectTooltip"
});
}
else if (leadSource === 8) {
// 3 = Web
subjectControl.setLabel("Landing Page");
subjectControl.addNotification({
messages: ["Provide the landing page URL"],
notificationLevel: "RECOMMENDATION",
uniqueId: "subjectTooltip"
});
}
else {
// Default
subjectControl.setLabel("Subject");
}
}
While addNotification isn’t a perfect replacement for a native tooltip, it’s a practical workaround when we need dynamic, context-aware user guidance.
Recently, we had a requirement to track the Current and Previous contracts for a Contact in our Dataverse environment. To achieve this, we created two separate N:1 relationships between the Contact and Contract tables
custom_currentcontractid → Contact’s Current Contract
Soon after, we noticed a strange issue: “When creating a Contact from a Contract record (via the Quick Create form), both Current Contract and Previous Contract fields were being automatically populated — with the same Contract record”. This was unexpected, especially since neither field was present on the Quick Create form!
After saving and closing, when we open the record, we can see both the lookup auto-populated with the contract record in context.
On adding these lookups in the Quick Create form, we can see that Dataverse is auto-populating it with the contract in context.
When we open a Quick Create form from a record (in our case, from a Contract), Dataverse passes the entity reference context to the Quick Create form. And here’s the catch, If the target entity (Contact) has multiple lookups to the source entity (Contract), Dataverse tries to populate them all.
This behavior is based on relationship metadata, not on what’s visible on the form. So even though we didn’t include the Current Active Contract or Previous Contract on the Quick Create form, Dataverse filled both with the same value.
If we have the fields on the quick create form we can make use of JavaScript on the onload to clear the values.
function clearBothContractLookups(executionContext) {
var formContext = executionContext.getFormContext();
// Check if in Quick Create mode (formType = 1)
if (formContext.ui.getFormType() === 1) {
var parentRecord = formContext.data.entity.getEntityReference();
// If opened from a Contact, clear BOTH lookups
if (parentRecord && parentRecord.entityType === "contact") {
// Clear Current Contract
if (formContext.getAttribute("new_currentcontract")) {
formContext.getAttribute("new_currentcontract").setValue(null);
}
// Clear Previous Contract
if (formContext.getAttribute("new_previouscontract")) {
formContext.getAttribute("new_previouscontract").setValue(null);
}
}
}
}
However, like in our case as we did not have these fields on the quick create form, and we didn’t want to have these populated during the creation of the Contract, as these fields were supposed to be populated later, we wrote a Pre-Create Plugin on Pre Operation for it.
public class ClearBothContractLookups : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
Entity contact = (Entity)context.InputParameters["Target"];
// Clear BOTH fields if they exist
if (contact.Contains("new_currentcontract"))
contact["new_currentcontract"] = null;
if (contact.Contains("new_previouscontract"))
contact["new_previouscontract"] = null;
// or remove them from the input parameters
contact.Attributes.Remove("new_currentcontract");
contact.Attributes.Remove("new_previouscontract");
}
}
}
}
Recently while trying to import the product records in our Dynamics 365 Sales, we got the below error – “The Default Unit is not a member of the specified Unit Group”
We were providing the correct UoM Group and Default UoM in our Excel file to be imported.
After some troubleshooting, we realized that we had 2 units with the same name “Primary Unit”, because of which the system was not able to identify the correct unit to be used during the import.
To fix this issue, we replaced the Name with the Guid of the record in our excel file.
This fixed the issue for us and we were able to import the product records successfully.
Had written a post on deleting the elastic table record earlier – How to Delete Elastic Table Records in Dataverse (SDK). We need to pass the partitionid value through an alternate key to delete the elastic table records. We will have the alternate key auto-created by the system when we create an elastic table. Earlier specifying an Alternate Key was not possible for the Delete action in the CDS Destination component. However, with the latest release (Version 25.1 – April 4, 2025), the Manually Specify and Alternate Key matching criteria support has been added when performing the Delete action as shown below. We also need to have the Use Homogeneous Batch Operation Message option checked, this uses DeleteMultiple request in the background.
On running the package, now we can see the elastic table records (having partitionid) getting deleted successfully.
After the successful run (here we are deleting 40K records), we can see the records deleted.