← blog
#AL#Business Central#ERP#Dynamics 365

Getting Started with AL Extensions in Business Central

2024-03-15·4 min read·Sujan Rai

Microsoft Dynamics 365 Business Central is a powerful ERP platform, and its extensibility model — powered by the AL programming language — is what makes it genuinely adaptable to real business needs. In this post I'll walk through everything you need to start building AL extensions from scratch.

What Is an AL Extension?

An AL extension is a compiled package (.app file) that extends or customizes Business Central's behavior without modifying the base application. This is the official, supported path — as opposed to the C/AL modifications that existed in classic Dynamics NAV.

Extensions are:

  • Upgrade-safe — they don't break when BC receives platform updates
  • Isolated — they can be cleanly installed, uninstalled, or rolled back
  • Publishable — to Microsoft AppSource or directly to your own tenant

Setting Up Your Environment

You'll need three things:

  1. VS Code + the AL Language extension
  2. Docker Desktop — for a local Business Central sandbox container
  3. BcContainerHelper — a PowerShell module that makes container management easy
# Install BcContainerHelper
Install-Module BcContainerHelper -Force

# Spin up a BC sandbox container
New-BcContainer `
  -accept_eula `
  -containerName "bcdev" `
  -artifactUrl (Get-BcArtifactUrl -type sandbox -country base) `
  -auth NavUserPassword `
  -Credential (New-Object PSCredential "admin", `
    (ConvertTo-SecureString "P@ssword1" -AsPlainText -Force))

Once the container is running, open VS Code and use Ctrl+Shift+PAL: Go! to scaffold a new extension project and connect it to your container.

Project Structure

A minimal AL extension looks like this:

my-extension/
├── app.json          ← manifest: ID, name, version, dependencies
├── .alpackages/      ← downloaded symbol packages
└── src/
    ├── TableExt.al
    ├── PageExt.al
    └── Codeunit.al

app.json

{
  "id": "12345678-1234-1234-1234-123456789012",
  "name": "My First Extension",
  "publisher": "Sujan Rai",
  "version": "1.0.0.0",
  "platform": "22.0.0.0",
  "application": "22.0.0.0",
  "idRanges": [{ "from": 50100, "to": 50199 }],
  "dependencies": []
}

Always define an idRanges block. Object IDs must be unique within your tenant — using a per-publisher range avoids collisions.

Your First Table Extension

Let's add a custom field to the Customer table:

tableextension 50100 "Customer Ext." extends Customer
{
    fields
    {
        field(50100; "Loyalty Points"; Integer)
        {
            Caption = 'Loyalty Points';
            DataClassification = CustomerContent;
        }
        field(50101; "Loyalty Tier"; Enum "Loyalty Tier")
        {
            Caption = 'Loyalty Tier';
            DataClassification = CustomerContent;
        }
    }
}

DataClassification is mandatory — Business Central enforces GDPR classification on all fields.

Page Extension

Expose the new fields on the Customer Card:

pageextension 50100 "Customer Card Ext." extends "Customer Card"
{
    layout
    {
        addafter("Credit Limit (LCY)")
        {
            group(Loyalty)
            {
                Caption = 'Loyalty';
                field("Loyalty Points"; Rec."Loyalty Points")
                {
                    ApplicationArea = All;
                    ToolTip = 'Specifies the customer loyalty points balance.';
                }
                field("Loyalty Tier"; Rec."Loyalty Tier")
                {
                    ApplicationArea = All;
                    ToolTip = 'Specifies the customer loyalty tier.';
                }
            }
        }
    }
}

Business Logic in a Codeunit

Keep your logic out of pages — put it in a codeunit:

codeunit 50100 "Loyalty Points Mgt."
{
    procedure AddPoints(CustomerNo: Code[20]; Points: Integer)
    var
        Customer: Record Customer;
    begin
        if not Customer.Get(CustomerNo) then
            Error('Customer %1 not found.', CustomerNo);

        Customer."Loyalty Points" += Points;
        UpdateTier(Customer);
        Customer.Modify(true);
    end;

    local procedure UpdateTier(var Customer: Record Customer)
    begin
        case true of
            Customer."Loyalty Points" >= 10000:
                Customer."Loyalty Tier" := Customer."Loyalty Tier"::Platinum;
            Customer."Loyalty Points" >= 5000:
                Customer."Loyalty Tier" := Customer."Loyalty Tier"::Gold;
            Customer."Loyalty Points" >= 1000:
                Customer."Loyalty Tier" := Customer."Loyalty Tier"::Silver;
            else
                Customer."Loyalty Tier" := Customer."Loyalty Tier"::Bronze;
        end;
    end;
}

Key Patterns

Filtering and FINDSET

var
    Customer: Record Customer;
begin
    Customer.SetRange(Blocked, Customer.Blocked::" ");
    Customer.SetFilter("Loyalty Points", '>%1', 500);

    if Customer.FindSet() then
        repeat
            // process Customer
        until Customer.Next() = 0;
end;

Subscribing to Events

Instead of modifying base app code, subscribe to published events:

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnAfterPostSalesDoc', '', false, false)]
local procedure OnAfterSalesPost(var SalesHeader: Record "Sales Header")
var
    LoyaltyMgt: Codeunit "Loyalty Points Mgt.";
begin
    if SalesHeader."Document Type" = SalesHeader."Document Type"::Invoice then
        LoyaltyMgt.AddPoints(SalesHeader."Sell-to Customer No.", 100);
end;

Publishing

Ctrl+Shift+P → AL: Publish Without Debugging   (fast, no debugger attached)
F5                                              (publish + attach debugger)

What to Explore Next

Once you're comfortable with table and page extensions:

  • API pages — expose BC data as REST endpoints with PageType::API
  • Report extensions — customize RDLC layout and dataset
  • Integration events — build loosely-coupled integrations between extensions
  • Azure Functions + BC webhooks — event-driven external integrations

AL development has a learning curve, but the extensibility model is clean and disciplined once it clicks. The official AL docs are your best reference.

Feel free to reach out if you have questions — I'm always happy to talk BC development.