AX 7. URLUtility extension to create deep links.

In AX 7 we can use URL to open any form and it’s quite handy. Out of the box there is a feature called “Get link” that helps us to generate and share links. We can find it under Options -> Share -> Get Link.


However, it has a limitation – you cannot generate link to open a specific record (so called “deep links”).

We can generate deep links from code by using URLGenerator class (there is a wiki article with explanations ), but it requires quite a bit of code to write. So I did small extension to URLUtility class to simplify this process.

It adds two new methods: generateRecordUrlFromDataSource and generateRecordUrl.

  • generateRecordUrlFromDataSource accepts form data source and generates a link to the record based on current record primary index fields.
  • generateRecordUrl accepts all parameters that are required to open a form, like menu item name and type, form data source name and map with key field’s IDs and values.
using Microsoft.Dynamics.AX.Framework.Utilities;
using Microsoft.Dynamics.@Client.ServerForm.Contexts;

/// <summary>
/// The class <c>URLUtility_Extension</c> contains extension methods for the <c>URLUtility</c> class.
/// </summary>
public static class URLUtility_Extension
    public static str generateRecordUrl(str _menuItemName, MenuItemType _menuItemType, DataSourceName _dataSourceName, Map _indexFieldValuesMap, DataAreaId _dataAreaId = curExt())
        System.Uri host                     = SessionContext::Get_Current().Get_RequestUrl();
        UrlHelper.UrlGenerator generator    = new UrlHelper.UrlGenerator();
        generator.MenuItemName              = _menuItemName;
        generator.MenuItemType              = _menuItemType;
        generator.HostUrl                   = host.GetLeftPart(System.UriPartial::Path);
        generator.Company                   = _dataAreaId;
        generator.EncryptRequestQuery       = true;

        if (_dataSourceName && _indexFieldValuesMap)
            MapEnumerator mapEnumerator = _indexFieldValuesMap.getEnumerator();

            var requestQueryParameterCollection = generator.RequestQueryParameterCollection;

            while (mapEnumerator.moveNext())
                requestQueryParameterCollection.UpdateOrAddEntry(_dataSourceName, mapEnumerator.currentKey(), mapEnumerator.currentValue());

        return generator.GenerateFullUrl().AbsoluteUri;

    public static str generateRecordUrlFromDataSource(FormDataSource _formDataSource)
        FormRun         formRun         = _formDataSource.formRun();
        str             menuItemName    = formRun.args().menuItemName();
        MenuItemType    menuItemType    = formRun.args().menuItemType();
        DataSourceName  dataSourceName  =;

        TableId   tableId   = _formDataSource.table();
        DictTable dictTable = new DictTable(tableId);
        DictIndex dictIndex = new DictIndex(tableId, dictTable.primaryIndex());

        int     fieldCount          = dictIndex.numberOfFields();
        Map     indexFieldValuesMap = new Map(Types::String, Types::String);
        Common  record              = _formDataSource.cursor();
        FieldId primaryKeyFieldId;

        for (int fieldIndex = 1; fieldIndex <= fieldCount; fieldIndex++)
            primaryKeyFieldId = dictIndex.field(fieldIndex);

            indexFieldValuesMap.insert(fieldId2Name(tableId, primaryKeyFieldId), any2Str(record.(primaryKeyFieldId)));

        return URLUtility::generateRecordUrl(menuItemName, menuItemType, dataSourceName, indexFieldValuesMap, record.DataAreaId);

To test this class, you can use simple job:

class RunnableClass1
    public static void main(Args _args)
        Map indexFieldValuesMap = new Map(Types::String, Types::String);
        indexFieldValuesMap.insert(fieldStr(VendTable, AccountNum), '1002');

        Box::info(URLUtility::generateRecordUrl(menuItemDisplayStr(VendTable), MenuItemType::Display, identifierStr(VendTable), indexFieldValuesMap, 'usmf'));

It’s an initial version, so feel free to post comments in case of any bugs!