4.4: Enums, Enum Extensions & Extensible Types

Key Takeaways

  • AL Enums are first-class, strongly typed objects that replace legacy Option fields to enable modular extensibility across independent extensions.
  • The Extensible property on an enum controls whether third-party apps can introduce new values via enumextension objects.
  • AssignmentCompatibility enables implicit assignment between enums and legacy option or integer values, easing backward compatibility during refactoring.
  • Enums can bind directly to AL interfaces using the implements keyword, with DefaultImplementation and UnknownValueImplementation handling fallback execution.
  • Ordinal collision avoidance mandates strict range management and gap numbering strategies across AppSource and Per-Tenant extensions.
Last updated: August 2026

4.4 Enums, Enum Extensions & Extensible Types

Prior to the introduction of AL Enums, Business Central utilized Option data types to represent fixed lists of values (e.g., Document Type::Invoice, Status::Open). Legacy Option fields suffered from a major architectural limitation: they were tightly coupled to the table or variable definition and could not be extended by third-party extensions.

Modern Business Central development replaces Options with AL Enums (enum objects) and Enum Extensions (enumextension objects). Enums provide extensible, strongly typed discrete values and serve as the architectural foundation for object-oriented polymorphism via AL Interfaces.


1. AL Enum Declaration & Object Structure

An Enum is declared as an independent AL object with a unique object ID and name. Each entry within an enum is declared as a value consisting of an integer Ordinal Number and an alphanumeric identifier.

Enum Declaration Syntax

enum 50120 "Reward Tier"
{
    Extensible = true;
    AssignmentCompatibility = true;
    Caption = 'Reward Tier';

    value(0; Bronze)
    {
        Caption = 'Bronze Member';
    }
    value(10; Silver)
    {
        Caption = 'Silver Member';
    }
    value(20; Gold)
    {
        Caption = 'Gold Member';
    }
    value(30; Platinum)
    {
        Caption = 'Platinum VIP';
    }
}

Enum Object Properties

  1. Extensible (Boolean):
    • Extensible = true (Default): Permits other extensions to declare enumextension objects that add new values to this enum.
    • Extensible = false: Locks the enum. No other extension can add new values. Used for core invariant business logic (such as accounting debit/credit indicators or system status codes).
  2. AssignmentCompatibility (Boolean):
    • When set to true, the AL compiler allows implicit assignment between this Enum and integer or legacy Option types without requiring explicit type casting ("Reward Tier"::Silver can be assigned from an integer or option value).
    • Essential when refactoring legacy base application Option fields to Enums while preserving backward compatibility with existing codeunits.
  3. Caption: Defines the user-facing localized display string for the enum object.
  4. UnknownValueImplementation / DefaultImplementation: Configures interface fallback implementations when binding enums to AL interfaces.
Loading diagram...
Enum Extension and Interface Implementation Architecture

2. Ordinal Allocation Strategy & Collision Avoidance

Every enum value requires an explicit integer ordinal. In the underlying SQL database, Business Central stores the integer ordinal rather than the text identifier.

Ordinal Allocation Rules & Best Practices

  • PTE vs AppSource Ranges: In Per-Tenant Extensions (PTE), custom enum values should use ordinals in the customer's allocated range (e.g., 50000..99999). In AppSource ISV packages, ordinals must fall strictly within the publisher's registered Microsoft Partner Center range.
  • Gap Numbering Strategy: Always space out core enum values (e.g., 0, 10, 20, 30 instead of 0, 1, 2, 3). This enables future insertion of intermediate logical values without breaking existing stored ordinal mappings.
  • Collision Mechanics: If two independent extensions attempt to add an enum extension value using the same ordinal number to the same extensible base enum, a deployment collision error occurs during schema synchronization:
    Error: An item with the same key has already been added. Key: 50000
    
  • Stability Rule: Once an enum value with a given ordinal is published to a production environment, that ordinal must never be changed or reassigned. Changing an ordinal causes data corruption across all table records storing that value.

3. Enum Extensions (enumextension)

An enumextension object extends an existing extensible enum by adding new values without modifying the original enum definition.

Enum Extension Syntax

enumextension 50125 "Advanced Reward Ext" extends "Reward Tier"
{
    value(50100; Diamond)
    {
        Caption = 'Diamond Elite Member';
    }
    value(50101; BlackCard)
    {
        Caption = 'Black Card VIP';
    }
}

Capabilities and Limitations of Enum Extensions

  • Additive Only: Enum extensions can only add new values. They cannot delete, hide, or rename existing values declared in the base enum or other extensions.
  • Target Extensibility Check: An enum extension cannot target an enum whose property is set to Extensible = false.
  • Unique Identifiers: The identifier name (e.g., Diamond) and ordinal (50100) must be unique across the base enum and all active extensions in the tenant.

4. Binding Enums to AL Interfaces (Polymorphism Pattern)

One of the most powerful capabilities in AL is binding Enums to AL Interfaces. This design pattern replaces complex CASE statements with clean, extensible object-oriented polymorphism.

Step 1: Define the AL Interface

interface "IPaymentGateway"
{
    procedure ProcessPayment(SalesHeader: Record "Sales Header"): Boolean;
    procedure RefundPayment(SalesHeader: Record "Sales Header"): Boolean;
}

Step 2: Declare the Enum with Interface Implementation

enum 50130 "Payment Gateway Type" implements "IPaymentGateway"
{
    Extensible = true;
    DefaultImplementation = "IPaymentGateway" = "Manual Payment Impl";
    UnknownValueImplementation = "IPaymentGateway" = "Unknown Payment Handler";

    value(0; Manual)
    {
        Caption = 'Manual Cash/Check';
        Implementation = "IPaymentGateway" = "Manual Payment Impl";
    }
    value(10; CreditCard)
    {
        Caption = 'Standard Credit Card';
        Implementation = "IPaymentGateway" = "Credit Card Impl";
    }
}

Step 3: Implement the Interface in Codeunits

codeunit 50131 "Manual Payment Impl" implements "IPaymentGateway"
{
    procedure ProcessPayment(SalesHeader: Record "Sales Header"): Boolean
    begin
        Message('Processing manual payment for order %1', SalesHeader."No.");
        exit(true);
    end;

    procedure RefundPayment(SalesHeader: Record "Sales Header"): Boolean
    begin
        exit(true);
    end;
}

codeunit 50132 "Credit Card Impl" implements "IPaymentGateway"
{
    procedure ProcessPayment(SalesHeader: Record "Sales Header"): Boolean
    begin
        // Direct payment gateway API integration
        exit(true);
    end;

    procedure RefundPayment(SalesHeader: Record "Sales Header"): Boolean
    begin
        exit(true);
    end;
}

codeunit 50133 "Unknown Payment Handler" implements "IPaymentGateway"
{
    procedure ProcessPayment(SalesHeader: Record "Sales Header"): Boolean
    begin
        Error('Unrecognized payment gateway type configured on transaction.');
    end;

    procedure RefundPayment(SalesHeader: Record "Sales Header"): Boolean
    begin
        Error('Cannot refund unrecognized payment gateway.');
    end;
}

Step 4: Polymorphic AL Invocation

codeunit 50140 "Sales Post Hook"
{
    procedure ExecutePayment(SalesHeader: Record "Sales Header")
    var
        PaymentGateway: Interface "IPaymentGateway";
    begin
        // Cast the enum field directly to the interface variable
        PaymentGateway := SalesHeader."Payment Gateway Type";
        // Invoke polymorphic business logic without ANY 'CASE' statements
        PaymentGateway.ProcessPayment(SalesHeader);
    end;
}

DefaultImplementation vs UnknownValueImplementation

  • DefaultImplementation: Invoked when an enum value does not specify an explicit Implementation = ... mapping for that interface.
  • UnknownValueImplementation: Invoked when an unassigned, corrupted, or uninstalled extension ordinal value is passed into the interface variable at runtime. This prevents hard unhandled runtime crashes and provides graceful error handling.

5. Migrating Legacy Option Fields to Enums

When modernizing legacy C/AL solutions or refactoring custom extensions, converting Option fields to Enum fields must be executed without corrupting existing database data.

Migration Steps & Schema Synchronization

  1. Declare the Enum: Create a new AL enum object where the value ordinals match the zero-based index positions of the original OptionMembers list (0 = Open, 1 = Released, 2 = Pending Approval).
  2. Change Field Data Type: On the table definition, change field(10; Status; Option) to field(10; Status; Enum "Document Status").
  3. Preserve Ordinals: Because SQL Server stores both Option and Enum values as integer ordinals in the table column, matching the ordinals guarantees zero data loss during schema synchronization.
  4. Deprecation with Obsolete Properties: If deprecating an old enum value, use ObsoleteState = Pending, ObsoleteReason = '...', and ObsoleteTag = '...' to signal breaking changes to downstream subscribers.
value(99; DeprecatedOption)
{
    Caption = 'Deprecated Option';
    ObsoleteState = Pending;
    ObsoleteReason = 'Replaced by Cloud Provider API v2.';
    ObsoleteTag = '24.0';
}
Test Your Knowledge

A developer needs to create a new enum for document processing. The enum must allow other ISV extensions to add additional processing methods, and it must permit assignment from legacy integer values without compiler errors. Which enum declaration satisfies these requirements?

A
B
C
D
Test Your Knowledge

You are designing an extensible architecture that uses an AL Enum bound to an Interface. If an external system transmits a record containing an ordinal value from an uninstalled extension, which property on the enum object specifies the fallback codeunit to execute to prevent runtime failure?

A
B
C
D
Test Your Knowledge

What happens if two independent AppSource extensions attempt to extend the same extensible base Enum by adding a value with the identical ordinal number (e.g., value(50000; CustomValue))?

A
B
C
D
Test Your Knowledge

When refactoring a legacy table field from Option to Enum in an AL extension, what is the critical technical rule to ensure that existing database data in production is not corrupted or altered?

A
B
C
D