8.2 Events, Publishers, Subscribers & Event Isolation

Key Takeaways

  • Event-driven architecture in AL decouples custom extension logic from the core Base Application, ensuring upgrade resilience without source code modification.
  • IntegrationEvent is the primary extension point for developers, whereas BusinessEvent represents an immutable public contract with long-term signature stability.
  • Event subscribers are declared using the [EventSubscriber] attribute, specifying the publisher object type, object ID, event name, element name, and licensing/permission flags.
  • Event isolation (Isolated = true) ensures that failing subscribers execute in isolated transaction boundaries, preventing errors from rolling back peer subscribers.
  • The IsHandled design pattern allows subscribers to intercept standard business routines and conditionally suppress default base application calculations or posting routines.
Last updated: August 2026

8.2 Events, Publishers, Subscribers & Event Isolation

The event-driven architecture is the cornerstone of modern Business Central extensibility. Prior to the AL extension model, customizations involved modifying base C/AL source code directly, creating complex merge conflicts during cumulative updates. In AL, developers cannot alter standard source files; instead, the platform exposes thousands of publisher events throughout core tables, pages, reports, and codeunits. Developers write subscriber procedures that listen for these events, injecting custom logic seamlessly without altering base code.


1. Event Publisher Types & Attributes

An event publisher is a procedure declaration decorated with an event attribute. Publisher procedures contain no AL body code (no begin...end block or variable definitions) and are invoked directly from operational code using their procedure name.

codeunit 50110 "Custom Order Processor"
{
    // 1. Integration Event Publisher
    [IntegrationEvent(true, false)]
    procedure OnBeforeProcessCustomOrder(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean)
    begin
    end;

    // 2. Business Event Publisher (Formal Contract)
    [BusinessEvent(false)]
    procedure OnCustomerCreditLimitExceeded(CustomerNo: Code[20]; OverdueBalance: Decimal)
    begin
    end;

    // 3. Internal Event Publisher (Module Scope)
    [InternalEvent(false)]
    procedure OnInternalValidationCompleted(DocumentNo: Code[20])
    begin
    end;

    // 4. Isolated Event Publisher
    [IntegrationEvent(false, false, true)]
    procedure OnAfterBroadcastNotification(NotificationId: Guid; MessageText: Text[250])
    begin
    end;

    procedure Execute(var SalesHeader: Record "Sales Header")
    var
        IsHandled: Boolean;
    begin
        // Raising the integration event
        OnBeforeProcessCustomOrder(SalesHeader, IsHandled);
        if IsHandled then
            exit;

        // Standard processing continues if not handled
        PerformStandardProcessing(SalesHeader);
    end;

    local procedure PerformStandardProcessing(var SalesHeader: Record "Sales Header")
    begin
        // Base processing logic
    end;
}

Comparing Event Publisher Attributes

AttributeScope / VisibilityPurpose & StabilityArguments
[IntegrationEvent]Global (all extensions).General-purpose extension point across tables, pages, and codeunits. Can evolve across major releases.IncludeSender: Boolean, GlobalVarAccess: Boolean, Isolated: Boolean
[BusinessEvent]Global (all extensions).Formal business contract (e.g., invoice posted, payment released). Guarantees permanent signature stability.IncludeSender: Boolean, Isolated: Boolean
[InternalEvent]Module-internal only.Internal communication within the same extension package. Not visible to external apps unless granted via internalsVisibleTo.IncludeSender: Boolean, Isolated: Boolean

Publisher Arguments Explained

  • IncludeSender: Boolean: When set to true, the publisher signature implicitly exposes a sender parameter of type Codeunit "PublisherName" or Record "TableName" to subscribers, granting access to the publishing instance's public methods.
  • GlobalVarAccess: Boolean: Legacy setting allowing subscribers to read and modify global variables of the publishing object. Marked obsolete in modern AL development and restricted to false in cloud-compliant apps.
  • Isolated: Boolean: When set to true, the platform executes each subscriber in its own isolated transaction boundary. If one subscriber encounters an unhandled runtime error, the engine catches the error, rolls back only that subscriber's changes, logs the error, and proceeds to execute all remaining subscribers without failing the overall transaction.
Loading diagram...
Event Subscriber Invocation Pipeline & IsHandled Design Pattern

2. Event Subscribers in AL

An event subscriber is a procedure decorated with the [EventSubscriber] attribute that listens for a specific event raised by an object in the system.

codeunit 50115 "Sales Post Subscriber"
{
    EventSubscriberInstance = StaticAutomatic; // Default behavior

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnAfterPostSalesDoc', '', true, true)]
    local procedure HandleOnAfterPostSalesDoc(
        var SalesHeader: Record "Sales Header";
        var GenJnlPostLine: Codeunit "Gen. Jnl.-Post Line";
        SalesShptHdrNo: Code[20];
        SalesInvHdrNo: Code[20];
        SalesCrMemoHdrNo: Code[20];
        InvHdrBuffer: Record "Invoice Post. Buffer")
    begin
        if SalesInvHdrNo <> '' then
            SendCustomInvoiceEmailNotification(SalesInvHdrNo);
    end;

    local procedure SendCustomInvoiceEmailNotification(InvoiceNo: Code[20])
    begin
        // Custom notification workflow
    end;
}

[EventSubscriber] Parameter Breakdown

[EventSubscriber(ObjectType, ObjectId, EventName, ElementName, SkipOnMissingLicense, SkipOnMissingPermission)]
  1. ObjectType: The AL object type publishing the event (ObjectType::Table, ObjectType::Page, ObjectType::Codeunit, ObjectType::Report, ObjectType::XMLport).
  2. ObjectId: The symbolic object identifier (e.g., Codeunit::"Sales-Post" or Database::Customer).
  3. EventName: String literal naming the publisher procedure (e.g., 'OnAfterPostSalesDoc').
  4. ElementName: Target field name or control name when subscribing to table field triggers (OnAfterValidate) or page control triggers. Empty string '' for object-level events.
  5. SkipOnMissingLicense: If true, the runtime ignores this subscriber silently if the tenant license does not cover the publishing object, preventing runtime licensing crashes.
  6. SkipOnMissingPermission: If true, the subscriber is skipped without error if the current user lacks read/write permissions to the publishing object or its underlying tables.

Static vs. Manual Event Subscribers

By default, codeunits have EventSubscriberInstance = StaticAutomatic, meaning all subscribers inside the codeunit are bound automatically upon tenant startup. However, developers can configure manual binding:

  • EventSubscriberInstance = Manual: The codeunit's subscribers are inactive by default.
  • BindSubscription(CodeunitInstance): Dynamically activates the subscribers on the specific codeunit instance at runtime.
  • UnbindSubscription(CodeunitInstance): Deactivates the subscribers.
  • Use Cases: Critical for automated testing (binding mock subscribers during specific test functions) and transient workflow interception.

3. Standard Platform Trigger Events & The IsHandled Pattern

Built-in Table and Page Trigger Events

Every table and page in Business Central emits standard platform trigger events automatically without requiring manual publisher code:

  • Table Trigger Events: OnBeforeInsertEvent, OnAfterInsertEvent, OnBeforeModifyEvent, OnAfterModifyEvent, OnBeforeDeleteEvent, OnAfterDeleteEvent, OnBeforeRenameEvent, OnAfterRenameEvent, OnBeforeValidateEvent, OnAfterValidateEvent.
  • Trigger Event Parameters: Built-in table events provide var Rec: Record TableName, var xRec: Record TableName, and RunTrigger: Boolean.
  • Page Trigger Events: OnBeforeOpenPageEvent, OnAfterOpenPageEvent, OnClosePageEvent, OnQueryClosePageEvent, OnBeforeValidateEvent, OnAfterValidateEvent.

The IsHandled Design Pattern

The IsHandled pattern allows an extension to completely replace standard Business Central calculations, document routing, or posting routines without modifying core objects.

// 1. Core Base Application Publisher Pattern
procedure CalculateTax(var SalesLine: Record "Sales Line")
var
    IsHandled: Boolean;
begin
    IsHandled := false;
    OnBeforeCalculateTax(SalesLine, IsHandled);
    if IsHandled then
        exit;

    // Standard internal tax engine executes here
    SalesLine."VAT %" := 20.0;
    SalesLine."Amount Including VAT" := SalesLine.Amount * 1.2;
end;

// 2. Extension Subscriber Overriding Standard Behavior
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Tax Management", 'OnBeforeCalculateTax', '', false, false)]
local procedure OverrideWithThirdPartyTaxEngine(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
var
    ExternalTaxService: Codeunit "External Tax Service";
begin
    if IsHandled then
        exit; // Another subscriber already handled this operation

    SalesLine."VAT %" := ExternalTaxService.GetCalculatedRate(SalesLine."No.", SalesLine."Tax Group Code");
    SalesLine."Amount Including VAT" := SalesLine.Amount * (1 + (SalesLine."VAT %" / 100));
    IsHandled := true; // Suppresses standard base tax calculation
end;

Exam Watchout — Execution Order Non-Determinism: If multiple extensions subscribe to the exact same event publisher, the execution order of the subscribers is non-deterministic. Always check if IsHandled then exit; at the start of your subscriber procedure to ensure you do not overwrite another extension's handling inadvertently.

Test Your Knowledge

A developer writes an event subscriber to integrate with an optional third-party ISV module. If the customer does not have a license for the third-party ISV codeunit, the subscriber must fail silently instead of throwing a runtime licensing error. How should the developer configure the [EventSubscriber] attribute?

A
B
C
D
Test Your Knowledge

When utilizing the 'IsHandled' design pattern to replace a standard base application calculation with a custom tax engine, what action must the subscriber take?

A
B
C
D
Test Your Knowledge

What is the key functional difference between [IntegrationEvent] and [BusinessEvent] attributes in AL?

A
B
C
D
Test Your Knowledge

A developer needs an event subscriber codeunit to remain inactive during normal user operations, but be dynamically activated and deactivated during specific automated test execution. Which codeunit property and AL methods should be used?

A
B
C
D