Automatically create dimensions, it’s not that hard!

In this video, I show how you can auto-create new dimension values and populate the dimension set with your new dimension value. Check it out:

https://youtu.be/vNGEFtcjAbs

In this video, Erik walks through a practical customization that he’s implemented countless times across many projects: automatically creating dimension values in Business Central. Instead of requiring accountants to manually maintain dimension values when data changes, we can write a small amount of AL code to handle this automatically. The video covers two related techniques: auto-creating dimension values from Job records, and auto-applying those dimensions to transactions like Sales Orders.

The Problem: Manual Dimension Maintenance

Dimensions in Business Central are a powerful tool for enabling deep reporting within the General Ledger module. However, dimension values often change over time — new projects are created, departments are reorganized, and so on. In many cases, the data that should drive these dimension values already exists in the system (for example, in the Jobs table), but someone still has to manually go in and create the corresponding dimension value. This is tedious, error-prone, and entirely automatable.

Setting Up the Scenario

For this example, Erik creates a new dimension called PROJECT in Business Central. The goal is straightforward: whenever a Job record is created or modified, the system should automatically create (or update) a corresponding dimension value under the PROJECT dimension. The Job’s number becomes the dimension value code, and the Job’s description becomes the dimension value name.

Auto-Creating Dimension Values from Jobs

The implementation uses a table extension on the Job table. Erik initially considers using the OnAfterValidate trigger on the Job Number field, but quickly realizes that at the point of number assignment, we don’t yet have a description to use as the dimension value name. So he pivots to the OnAfterModify trigger instead — this fires whenever the Job record is modified, meaning we’ll have access to the description.

tableextension 50100 MyJob extends Job
{
    trigger OnAfterModify()
    var
        DimValue: Record "Dimension Value";
    begin
        if Rec.Description <> '' then begin
            if not DimValue.Get('PROJECT', Rec."No.") then begin
                DimValue.Init();
                DimValue."Dimension Code" := 'PROJECT';
                DimValue.Code := Rec."No.";
                DimValue.Insert(True);
            end;
            DimValue.validate(Name, copystr(Rec.Description, 1, MaxStrLen(DimValue.Name)));
            DimValue.Modify(true);
        end;
    end;
}

Understanding the Pattern

There’s a useful pattern at work here that Erik highlights. The code handles two scenarios with a single flow:

  1. The dimension value doesn’t exist yet: The Get call returns false, so we initialize a new record, set the primary key fields (Dimension Code and Code), and insert it.
  2. The dimension value already exists: The Get call returns true, so we skip the insert block entirely.

In both cases, after this if/then block, we have a valid DimValue record in hand. We can then validate the Name field and modify the record. This means if someone renames a Job, the dimension value name gets updated automatically too.

A few other details worth noting:

  • The code only runs if the Job has a description (Rec.Description <> ''), since there’s no point creating a dimension value without a meaningful name.
  • The Job description field is 100 characters, but the dimension value Name field is only 50, so CopyStr with MaxStrLen is used to safely truncate.
  • Insert(True) and Modify(True) are used to ensure any standard validation triggers on the Dimension Value table still fire.

Auto-Applying Dimensions to Transactions

Creating dimension values automatically is only half the story. The PROJECT dimension isn’t necessarily a global or shortcut dimension, so how do we actually get it onto transactions? Erik builds on a technique from a previous video (about the only procedure you ever need for working with dimension sets) to add a Job Number field to the Sales Header and automatically update the document’s dimension set when a job is selected.

The Sales Header Table Extension

tableextension 50101 "Sales Header" extends "Sales Header"
{
    fields
    {
        field(50100; JobNo; Code[20])
        {
            Caption = 'Job';
            TableRelation = Job."No.";
            trigger OnValidate()
            var
                dims: Record "Dimension Set Entry" temporary;
            begin
                dims.init();
                dims.validate("Dimension Code", 'PROJECT');
                dims.validate("Dimension Value Code", Rec.JobNo);
                Dims.Insert();
                UpdateDimSetOnSalesHeader(Rec, Dims);
                Rec.Modify();
            end;
        }
    }
    procedure UpdateDimSetOnSalesHeader(var SH: Record "Sales Header"; var ToAddDims: Record "Dimension Set Entry" temporary)
    var
        DimMgt: Codeunit DimensionManagement;
        NewDimSet: Record "Dimension Set Entry" temporary;
    begin
        DimMgt.GetDimensionSet(NewDimSet, SH."Dimension Set ID");
        if ToAddDims.FindSet() then
            repeat
                if NewDimSet.Get(SH."Dimension Set ID", ToAddDims."Dimension Code") then begin
                    NewDimSet.validate("Dimension Value Code", ToAddDims."Dimension Value Code");
                    NewDimSet.Modify();
                end else begin
                    NewDimSet := ToAddDims;
                    NewDimSet."Dimension Set ID" := SH."Dimension Set ID";
                    NewDimSet.Insert();
                end;
            until ToAddDims.Next() = 0;
        SH."Dimension Set ID" := DimMgt.GetDimensionSetID(NewDimSet);
        DimMgt.UpdateGlobalDimFromDimSetID(SH."Dimension Set ID", SH."Shortcut Dimension 1 Code", SH."Shortcut Dimension 2 Code");
    end;
}

How It Works

When a user enters a Job Number on the Sales Order, the OnValidate trigger fires and:

  1. Creates a temporary Dimension Set Entry record representing the dimension we want to add (PROJECT with the selected job’s number as the value).
  2. Calls UpdateDimSetOnSalesHeader, which merges this new dimension into the document’s existing dimension set.

The UpdateDimSetOnSalesHeader procedure handles the merge logic:

  • It retrieves the current dimension set entries using DimMgt.GetDimensionSet.
  • For each dimension we want to add, it checks if that dimension already exists in the set. If so, it updates the value; if not, it inserts a new entry.
  • It then calls DimMgt.GetDimensionSetID to get (or create) the proper Dimension Set ID for the resulting combination.
  • Finally, it updates the global dimension fields on the Sales Header in case the modified dimensions are global dimensions.

The Page Extension

To make the new field visible, a simple page extension adds the Job Number field to the Sales Order page:

pageextension 50100 "Sales Order" extends "Sales Order"
{
    layout
    {
        addafter("No.")
        {
            field(JobNo; Rec.JobNo)
            {
                ApplicationArea = all;
            }
        }
    }
}

Seeing It in Action

After deploying the extension, Erik demonstrates the full workflow:

  1. Opening an existing Job and modifying it — the dimension value appears automatically in the PROJECT dimension.
  2. Using the “Next” function on other Jobs to trigger modifications, creating their dimension values as well.
  3. Creating a brand new Job — it only gets a dimension value once a description is added and the record is modified.
  4. Opening a Sales Order, entering a Job Number in the new field, and confirming that the PROJECT dimension with the correct value now appears in the document’s dimension set.

Summary

This video demonstrates two complementary techniques that Erik uses regularly to make life easier for customers:

  • Auto-creating dimension values: A table extension with an OnAfterModify trigger that ensures dimension values are always in sync with source data (in this case, Jobs). The “get-or-create” pattern keeps the code clean and handles both new and updated records.
  • Auto-applying dimensions to transactions: A reusable procedure that merges custom dimensions into an existing dimension set on documents like Sales Orders, handling both new and existing dimension entries in the set.

Together, these patterns eliminate manual dimension maintenance and ensure dimensions are consistently applied across transactions — all with a surprisingly small amount of AL code. This approach could also be triggered at other points, such as during document release, depending on your business requirements.