How-ToAL Development

Use Events and Subscribers in AL Development

Understand the publisher/subscriber event model in AL, subscribe to standard Business Central events, and declare and raise your own integration events.

8 min read

The event model in Business Central AL is the standard way to extend base application logic without modifying Microsoft’s code. Instead of overriding a procedure, you subscribe to an event that fires at a specific point in the base code and run your own logic there. This keeps your extension upgrade-safe and avoids merge conflicts with future BC updates.

This post covers the publisher/subscriber pattern, the two main event types, how to subscribe to an existing BC event, and how to declare and raise your own integration event.


Prerequisites

  1. Visual Studio Code with the AL Language extension installed
  2. A Business Central sandbox with your extension deployed
  3. An existing AL extension project with app.json configured and symbols downloaded
  4. Basic understanding of AL codeunits and procedures

Steps

1. Understand the publisher/subscriber pattern

An event publisher is a procedure marked with an attribute that declares a named event. The publisher procedure itself contains no logic, it is just a signal point.

An event subscriber is a procedure in a separate codeunit that listens for a specific event and runs when that event fires.

The two event types you will use most often are:

  • Integration Event, a general-purpose event that any subscriber can listen to. Used for communication between extensions or with custom logic.
  • Business Event, a semantic event that represents a meaningful business action (e.g. a sales order was posted). These carry a stronger contract and should not change their signature.

In standard BC base application code, Microsoft publishes events at key points in nearly every process. You subscribe to those events from your extension.


2. Subscribe to an existing Business Central event

The Sales Post codeunit publishes OnAfterPostSalesOrder. This fires after a sales order is successfully posted. To subscribe, create a codeunit and add an [EventSubscriber] procedure.

codeunit 50200 "Sales Post Event Handler"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnAfterPostSalesOrder', '', false, false)]
    local procedure OnAfterPostSalesOrder(var SalesHeader: Record "Sales Header"; CommitIsSuppressed: Boolean)
    var
        PostedOrderLog: Record "Posted Order Log";
    begin
        PostedOrderLog.Init();
        PostedOrderLog."Document No." := SalesHeader."Last Posting No.";
        PostedOrderLog."Customer No." := SalesHeader."Sell-to Customer No.";
        PostedOrderLog."Posted At" := CurrentDateTime();
        PostedOrderLog.Insert();
    end;
}

The [EventSubscriber] attribute takes five arguments:

ArgumentValue in example
Object typeObjectType::Codeunit
Object ID / nameCodeunit::"Sales-Post"
Event name'OnAfterPostSalesOrder'
Element name (optional)''
Skip on missing licensefalse
Skip on missing permissionfalse

The subscriber procedure signature must match the publisher’s parameters exactly, or compilation will fail.


3. Find published events in the base application

You do not need to memorise event names. In VS Code, open any base codeunit (e.g. Sales-Post) using Go to Definition (F12) or by browsing through the downloaded symbols. Look for procedures decorated with [IntegrationEvent] or [BusinessEvent].

You can also search the AL symbols using Ctrl + T and typing part of the event name.


4. Declare your own Integration Event

If you want other extensions (or other codeunits in your own extension) to hook into your logic, declare an [IntegrationEvent] in a publisher codeunit.

The publisher procedure must be empty, no code inside the begin...end block.

codeunit 50201 "Contact Note Events"
{
    [IntegrationEvent(false, false)]
    procedure OnAfterInsertContactNote(var ContactNote: Record "Contact Note")
    begin
    end;

    [IntegrationEvent(false, false)]
    procedure OnBeforeDeleteContactNote(var ContactNote: Record "Contact Note"; var IsHandled: Boolean)
    begin
    end;
}

The two boolean parameters on [IntegrationEvent] are:

  • IncludeSender, if true, the codeunit instance that raised the event is passed to the subscriber. Usually false.
  • GlobalVarAccess, if true, global variables in the publishing codeunit are accessible to subscribers. Usually false.

5. Raise the event from your code

Raising an event means calling the publisher procedure at the right point in your logic. Create an instance of the publisher codeunit and call the procedure.

codeunit 50202 "Contact Note Manager"
{
    procedure InsertNote(CustomerNo: Code[20]; NoteText: Text[250])
    var
        ContactNote: Record "Contact Note";
        ContactNoteEvents: Codeunit "Contact Note Events";
    begin
        ContactNote.Init();
        ContactNote."Customer No." := CustomerNo;
        ContactNote."Note Date" := Today();
        ContactNote.Description := NoteText;
        ContactNote."Created By" := UserId();
        ContactNote.Insert(true);

        ContactNoteEvents.OnAfterInsertContactNote(ContactNote);
    end;

    procedure DeleteNote(EntryNo: Integer)
    var
        ContactNote: Record "Contact Note";
        ContactNoteEvents: Codeunit "Contact Note Events";
        IsHandled: Boolean;
    begin
        ContactNote.Get(EntryNo);

        ContactNoteEvents.OnBeforeDeleteContactNote(ContactNote, IsHandled);
        if IsHandled then
            exit;

        ContactNote.Delete(true);
    end;
}

The IsHandled pattern on OnBeforeDeleteContactNote allows a subscriber to cancel the deletion by setting IsHandled := true. Always check IsHandled after raising a OnBefore event if you want subscribers to be able to interrupt the action.


6. Subscribe to your own event

Subscribing to your own event follows the same pattern as subscribing to a base application event.

codeunit 50203 "Contact Note Audit Handler"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Contact Note Events", 'OnAfterInsertContactNote', '', false, false)]
    local procedure LogNoteInsert(var ContactNote: Record "Contact Note")
    var
        AuditLog: Record "Contact Note Audit Log";
    begin
        AuditLog.Init();
        AuditLog."Entry No." := ContactNote."Entry No.";
        AuditLog."Customer No." := ContactNote."Customer No.";
        AuditLog."Action" := AuditLog."Action"::Inserted;
        AuditLog."Action DateTime" := CurrentDateTime();
        AuditLog.Insert();
    end;
}

7. Compile and test

Press Ctrl + Shift + B to compile. Publish with F5. Run the action that triggers your event (in this case, inserting a contact note via the Contact Note Card) and verify the subscriber logic executed as expected.

If the subscriber is not firing, check that:

  • The event name string in [EventSubscriber] exactly matches the publisher procedure name (case-sensitive)
  • The codeunit containing the subscriber is not excluded from compilation
  • The extension containing the publisher is installed and the binding resolves correctly

Common Mistakes

  • Putting code inside the publisher procedure body, the body must remain empty.
  • Mismatching the subscriber signature, every var parameter in the publisher must be var in the subscriber too.
  • Raising events inside a transaction and assuming subscribers will commit, subscribers run in the same transaction as the caller unless you explicitly commit.

Next Steps

Once your events are in place, your extension is ready to be packaged and deployed. See How to Publish and Install Extensions in Business Central for the full publish workflow.