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.
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
Extensible(Boolean):Extensible = true(Default): Permits other extensions to declareenumextensionobjects 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).
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"::Silvercan 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.
- When set to
Caption: Defines the user-facing localized display string for the enum object.UnknownValueImplementation/DefaultImplementation: Configures interface fallback implementations when binding enums to AL interfaces.
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,30instead of0,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 explicitImplementation = ...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
- Declare the Enum: Create a new AL enum object where the value ordinals match the zero-based index positions of the original
OptionMemberslist (0 = Open,1 = Released,2 = Pending Approval). - Change Field Data Type: On the table definition, change
field(10; Status; Option)tofield(10; Status; Enum "Document Status"). - 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.
- Deprecation with Obsolete Properties: If deprecating an old enum value, use
ObsoleteState = Pending,ObsoleteReason = '...', andObsoleteTag = '...'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';
}
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?
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?
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))?
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?