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
- Visual Studio Code with the AL Language extension installed
- A Business Central sandbox with your extension deployed
- An existing AL extension project with
app.jsonconfigured and symbols downloaded - 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:
| Argument | Value in example |
|---|---|
| Object type | ObjectType::Codeunit |
| Object ID / name | Codeunit::"Sales-Post" |
| Event name | 'OnAfterPostSalesOrder' |
| Element name (optional) | '' |
| Skip on missing license | false |
| Skip on missing permission | false |
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. Usuallyfalse. - GlobalVarAccess, if
true, global variables in the publishing codeunit are accessible to subscribers. Usuallyfalse.
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
varparameter in the publisher must bevarin 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.