Dynamics 365 F&O Technical Consultant
Subscribe to the newsletter
Deep links in Dynamics 365 Finance & Operations offer a powerful way to enhance navigation and user experience. By creating URLs that directly lead to specific forms, records, or pages within the application, deep links save time, reduce complexity, and enable seamless access to critical data.
Whether you’re an administrator configuring workflows or a developer streamlining processes, understanding how to use deep links effectively can significantly improve efficiency and usability.
This blog explores the concept of deep links, how they are created, and practical applications of deep links in Dynamics 365 Finance and Operations, along with best practices to maximize their impact.
What are deep links?
Deep links are URLs designed to take users directly to specific pages or functionalities in Dynamics 365 F&O. Unlike generic links to the system’s homepage, deep links incorporate parameters that point to specific forms, records, or actions. They are essential for:
- Reducing time spent navigating menus.
- Enhancing productivity in workflows and reporting.
- Facilitating integrations between D365 and external systems.
Deep links can optionally include a data context, allowing the form to display filtered or specific data when accessed. The URL Generator facilitates scenarios such as embedding links in reports, emails, or external applications, enabling users to quickly navigate to the desired forms or data using the generated links.
How do deep links work in Dynamics 365 Finance and Operations?
A typical deep link in Dynamics 365 F&O consists of:
- Base URL: The environment URL
(e.g., https://myd365environment.cloud.dynamics.com). - Menu Item (mi): The name of the form or action
(e.g., CustTable for the customer form). - Parameters: Additional query string parameters to filter or locate specific records
(e.g., CustAccount=1234).
Example structure: https:///?cmp=&mi=&
Example link:
https://myd365environment.cloud.dynamics.com/?cmp=USMF&mi=CustTable&CustAccount=001234
Step-by-step guide for creating deep links in Dynamics 365 Finance and Operations
This step-by-step guide walks you through the process of creating deep links in Dynamics 365 Finance and Operations for seamless navigation to specific forms and data.
Step 1: Identify the target form or action
Determine the form you want the link to open. Use the AOT in Visual Studio or the Application Explorer to locate the relevant menu item name. For example, to link to the Customer Details form, the menu item name is CustTable.
Step 2: Construct the URL
Combine your environment’s base URL with the required parameters. Here’s how:
- cmp: The legal entity or company code.
- mi: The menu item name.
- Additional parameters as needed, such as CustAccount for a customer account or VendAccount for vendors.
For example: https://myd365environment.cloud.dynamics.com/?cmp=USMF&mi=CustTable&CustAccount=001234
Step 3: Test thelLink
Paste the constructed URL into a browser. Then, verify that it navigates to the intended form with the correct filters applied.
Use cases for deep links
Here are some examples of different types of deep links:
- Workflow notifications
Provide approvers with a direct link to their pending tasks.
Example: https://myd365environment.cloud.dynamics.com/?mi=SysWorkflowWorkItemAction&WorkItemId=5678 - Emailalerts
Include deep links in notification emails to direct users to specific records or reports. - Custom reports
Use deep links to share filtered reports directly with team members.
Example: https://myd365environment.cloud.dynamics.com/?mi=CustAgingReport&CustGroup=Retail - External integrations
Share links with external stakeholders for direct access (ensuring proper security settings).
Best practices for generating deep links
Here are some of the best practices for generating deep links in Dynamics 365 Finance and Operations to ensure efficiency, security, and optimal performance.
- Maintain security: Ensure users have permissions to access the forms or records linked. Avoid exposing sensitive information in the URL parameters.
- Use descriptive parameters: Keep parameters meaningful to make links easy to read and maintain.
- Regularly validate links: Test links periodically to ensure they function after system updates or changes.
- Troubleshooting common issues in deep link creation: Learn how to identify and resolve frequent challenges when generating deep links in Dynamics 365 Finance and Operations for seamless navigation:
- Incorrect form or no access: Verify the mi value matches the menu item in the AOT. Ensure the user has necessary permissions.
- Missing data: Check that all required parameters are included in the URL.
- Environment URL issues: Confirm the base URL matches the current environment (e.g., production or sandbox).
Practical Implementation of deep links
Here is an example of how to add deep links to customer account IDon excel sheet.
First, create a batch job class in Dynamics 365 Finance and Operations that generates the Excel file with customer details and includes deep links for customer account IDs.
public class ExportCustomerDetailsToExcelBatch extends SysOperationServiceController
{
private str fileGenerationMessage;
public void createAndSendExcelFile(str fileName, Container headers, Container data)
{
System.IO.MemoryStream memoryStream = new System.IO.MemoryStream();
boolean dataFound = conLen(data) > 0;
if (dataFound)
{
try
{
// Create an ExcelPackage using the memory stream
using (var package = new OfficeOpenXml.ExcelPackage(memoryStream))
{
// Initialize row count
int row = 1;
// Add worksheet name
OfficeOpenXml.ExcelWorksheet worksheet = package.Workbook.Worksheets.Add(fileName);
OfficeOpenXml.ExcelRange cell;
// Write headers using get_Item approach
int headerIndex = 1;
while (headerIndex <= conLen(headers))
{
cell = worksheet.Cells.get_Item(row, headerIndex); // Accessing cell using get_Item
cell.Value = conPeek(headers, headerIndex);
headerIndex++;
}
// Write data using get_Item approach
int dataIndex = 1;
while (dataIndex <= conLen(data))
{
row++;
container rowData = conPeek(data, dataIndex);
int columnIndex = 1;
while (columnIndex <= conLen(rowData))
{
cell = worksheet.Cells.get_Item(row, columnIndex); // Accessing cell using get_Item
cell.Value = conPeek(rowData, columnIndex);
if (columnIndex == 1)
{
str recordId = conPeek(rowData, 1); // Customer ID
str url = CFZURLGeneration::generateDynamicsURL(
menuItemDisplayStr(CustTable),
MenuItemType::Display,
formDataSourceStr(CustTable, CustTable),
fieldstr(CustTable, AccountNum),
recordId);
cell.Hyperlink = new System.Uri(url); // Convert string to System.Uri
}
columnIndex++;
}
dataIndex++;
}
// Save the Excel package to memory stream
package.Save();
}
// Reset memory stream position to 0 before sending
memoryStream.Position = 0;
// Send the Excel file to the user
File::SendFileToUser(memoryStream, fileName);
// Set a message indicating that the file has been generated and sent to the user
this.fileGenerationMessage = "Please see the downloaded file for progress.";
}
catch (Exception::Error)
{
// Set an error message if file generation fails
this.fileGenerationMessage = "Failed to generate the file.";
}
}
else
{
// Set a message indicating no data found
this.fileGenerationMessage = "No data found for the given criteria.";
}
}
public str getFileGenerationMessage()
{
return this.fileGenerationMessage;
}
}
For downloading the excel file you need to write this class:
//downloading an Excel file containing customer data
// TODO: Comment out this class in the future. The current requirement is to open the form in the same tab with the same filtration logic, instead of downloading data in Excel.
/// /// This class represents a Copilot action for downloading an Excel file containing customer aging data /// based on a specified number of days. It retrieves customer aging data, inserts relevant account numbers /// into the CFZCopilotCustomerOnHold table, and generates a CSV file for download. ///
[DataContract]
[SysCopilotChatGlobalAction]
[SysCopilotChatActionDefinition(
‘MS.PA.RA.Copilot.CFZCustomerAging’, // It has to start with “MS.PA” for Identifier
‘Download Excel File containing Customer Aging by specified Days’, // Name for the copilot action
‘Present user with Download Excel File containing Customer Aging by specified Days’, // Description for the Action
menuItemActionStr(CFZCustomerAging), MenuItemType::Action)] // Secured by Menu Item
public class CFZCustomerAging extends SysCopilotChatAction
{
private int days;
private str copilotTopicConversationId;
private str customerAging;
// Input parameter that is received from Copilot Studio.
[DataMember('days'),
SysCopilotChatActionInputParameter('Days for which Data needed.', true)]
public int parmDays(int _days = days)
{
days = _days;
return days;
}
// Input parameter that is received from Copilot Studio.
[DataMember('copilotTopicConversationId'),
SysCopilotChatActionInputParameter('Conversation ID for Copilot Topic Data.', true)]
public str parmCopilotTopicConversationId(str _copilotTopicConversationId = copilotTopicConversationId)
{
copilotTopicConversationId = _copilotTopicConversationId;
return copilotTopicConversationId;
}
// Output parameter that is received by Copilot Studio once the action is executed.
[DataMember('customerAging'),
SysCopilotChatActionOutputParameter('Customer Aging Data')]
public str parmCustomerAging(str _customerAging = customerAging)
{
customerAging = _customerAging;
return customerAging;
}
/// <summary>
/// Executes the Copilot action to gather customer aging data.
/// </summary>
/// <param name="_actionDefinition">The action definition.</param>
/// <param name="_executionContext">The execution context.</param>
public void executeAction(SysCopilotChatActionDefinitionAttribute _actionDefinition, Object _executionContext)
{
// Initialize variables
CustTrans custTrans;
CFZCopilotCustomerOnHold cfzCopilotCustomerOnHold;
CFZExportFileData fileHandler = new CFZExportFileData();
customerAging = '';
int daysFromCopilot = this.parmDays();
str conversationIdFromCopilot = this.parmCopilotTopicConversationId();
if (daysFromCopilot)
{
Date dateThreshold = DateTimeUtil::date(DateTimeUtil::addDays(today(), -daysFromCopilot));
str fileName = 'Customer Aging.xlsx';
// Prepare headers as container
container headers = ['Customer ID', 'Customer Name', 'Invoice Number','Amount','Amount Not Settled' ,'Date','Aging Days'];
// Prepare data as container
Container data;
int dataIndex = 1;
// Delete all data from CFZCopilotCustomerOnHold table
ttsBegin;
delete_from cfzCopilotCustomerOnHold;
ttsCommit;
// Query to retrieve customer aging data
while select custTrans
order by custTrans.TransDate asc
where
custTrans.TransDate < dateThreshold
&& custTrans.Invoice != ""
{
if (custTrans.remainAmountCur() !=0) // This represents Open invoices (when blance isnot equal to zero then it means invoice is open)
{
// Calculate aging days
int agingDays = today() - custTrans.TransDate;
// Insert the AccountNum into CFZCopilotCustomerOnHold table
ttsBegin;
cfzCopilotCustomerOnHold.clear();
cfzCopilotCustomerOnHold.CFZAccountNum = custTrans.AccountNum;
cfzCopilotCustomerOnHold.CFZCopilotConversationId = conversationIdFromCopilot;
cfzCopilotCustomerOnHold.insert();
ttsCommit;
Container rowData = [custTrans.AccountNum,
custTrans.custTableName(),
custTrans.Invoice,
custTrans.AmountMST,
custTrans.remainAmountCur(),
any2str(custTrans.TransDate),
agingDays];
data = conIns(data, dataIndex , rowData);
dataIndex++;
// Concatenate or format customerAging string with retrieved data
//customerAging += strFmt("Customer ID: %1, Customer Name: %2, Invoice Number: %3, Amount: %4, Date: %5\n",
// custTrans.AccountNum,custTrans.custTableName(), custTrans.Invoice, custTrans.AmountMST, custTrans.TransDate);
}
}
fileHandler.createAndSendExcelFile(fileName, headers, data);
this.parmCustomerAging(fileHandler.getFileGenerationMessage());
}
else
{
// Handle error if dates are not provided
this.parmCustomerAging('@CFZCopilotLabels:ERRProvideDays');
}
}
}
Use this class for creating deep links on Customer Account ID:
// Link genration Class
using Microsoft.Dynamics.AX.Framework.Utilities;
/// /// Provides methods for constructing URLs within the Dynamics 365 environment. /// Includes functionality for generating full URLs based on menu items and optional query parameters. ///
public class CFZURLGeneration
{
/// <summary>
/// Builds a full URL for a specified menu item in the Dynamics 365 environment.
/// </summary>
/// <param name="_menuItemName">The name of the menu item to include in the URL.</param>
/// <param name="_menuItemType">The type of the menu item (e.g., action, display).</param>
/// <param name="_dataSource">Optional data source for the URL query parameters.</param>
/// <param name="_field">Optional field name for the URL query parameters.</param>
/// <param name="_value">Optional value for the URL query parameters.</param>
public static str generateDynamicsURL(MenuItemName _menuItemName, MenuItemType _menuItemtype, DataSourceName _dataSource='', FieldName _field='', str _value='')
{
try
{
str baseUrl = CFZURLGeneration::getBaseUrl(); // Retrieve the base URL of the Dynamics 365 environment
// Instantiate a new Uri object with the base URL
System.Uri currentHost = new System.Uri(baseUrl);
UrlHelper.UrlGenerator generator = new UrlHelper.UrlGenerator(); // Create a URL generator instance
generator.HostUrl = currentHost.GetLeftPart(System.UriPartial::Authority); // Set the host URL
generator.Company = curExt(); // Set the current company context
generator.MenuItemName = _menuItemName; // Set the menu item name
generator.MenuItemType = _menuItemtype; // Set the menu item type
generator.Partition = getCurrentPartition(); // Set the current partition
generator.EncryptRequestQuery = true; // Enable encryption for the request query
if (_dataSource != '') // Check if data source is provided
{
UrlHelper.RequestQueryParameterCollection requestQueryParameterCollection; // Create a collection for query parameters
requestQueryParameterCollection = generator.RequestQueryParameterCollection; // Retrieve the query parameter collection
requestQueryParameterCollection.AddRequestQueryParameter(_dataSource, _field, _value); // Add the query parameters
}
System.Uri fullURI = generator.GenerateFullUrl(); // Generate the full URL
return fullURI.AbsoluteUri; // Return the full URL as a string
}
catch (Exception ::Error)
{
// Log or handle the exception as needed
throw error("An error occurred while generating the URL"); // Throw an error with the exception message
}
}
/// <summary>
/// Retrieves the base URL of the Dynamics 365 environment.
/// </summary>
/// <returns>The base URL of the Dynamics 365 environment.</returns>
private static str getBaseUrl()
{
try{
var env = Microsoft.Dynamics.ApplicationPlatform.Environment.EnvironmentFactory::GetApplicationEnvironment(); // Get the application environment
// Check if the environment object is not null
if (env)
{
return env.Infrastructure.HostUrl; // Return the host URL from the environment
}
else
{
// Handle the case where the environment object is null
throw error("Failed to retrieve the host URL from the environment."); // Throw an error if the environment URL cannot be retrieved
}
}
catch (Exception::Error)
{
// Log or handle the exception as needed
throw error("An error occurred while retrieving the base URL"); // Throw an error with the exception message
}
}
}
Summing up
Deep links in Dynamics 365 Finance & Operations are powerful tools for streamlining navigation and enhancing user experience by directly linking to specific forms or records. By leveraging these links, businesses can improve efficiency and accessibility across teams. Generating deep links is straightforward, allowing users to quickly access the information they need with just a click.
For further assistance or inquiries, feel free to contact us at marketing@confiz.com.