Event Subscribers in AL
Build extension-safe customizations using event publishers and subscribers without modifying the base application.
Learning Objectives
- Subscribe to table, page, and codeunit events
- Use OnBefore and OnAfter event patterns
- Implement validation and business logic via event subscribers
Why Events?
Event subscribers let you extend Business Central without modifying Microsoft code. This is the primary customization pattern for AppSource apps and upgrade-safe extensions.
Publisher — Microsoft (or you) raises an event
Subscriber — Your code reacts to the event
Event Subscriber Syntax
codeunit 50103 "Customer Event Subscriber"
{
[EventSubscriber(ObjectType::Table, Database::Customer, 'OnBeforeInsertEvent', '', false, false)]
local procedure OnBeforeCustomerInsert(var Rec: Record Customer; RunTrigger: Boolean)
begin
if Rec.Name = '' then
Error('Customer name cannot be empty.');
end;
[EventSubscriber(ObjectType::Table, Database::Customer, 'OnAfterInsertEvent', '', false, false)]
local procedure OnAfterCustomerInsert(var Rec: Record Customer; RunTrigger: Boolean)
begin
LogCustomerCreation(Rec."No.");
end;
}Event Types
| Event Pattern | When It Fires |
|---|---|
OnBeforeInsertEvent | Before a record is inserted |
OnAfterInsertEvent | After a record is inserted |
OnBeforeModifyEvent | Before a record is modified |
OnAfterModifyEvent | After a record is modified |
OnBeforeDeleteEvent | Before a record is deleted |
OnAfterValidateEvent | After a field is validated |
Subscribing to Codeunit Events
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnBeforePostSalesDoc', '', false, false)]
local procedure OnBeforePostSalesDoc(var SalesHeader: Record "Sales Header")
begin
SalesHeader.TestField("Approval Status", SalesHeader."Approval Status"::Approved);
end;EventSubscriber Parameters
The last two boolean parameters control skip and priority. Use false, false for standard subscribers. Set priority when execution order matters.
Integration Events (Custom Publishers)
You can publish your own events for other extensions to subscribe to:
codeunit 50110 "Bonus Publisher"
{
[IntegrationEvent(false, false)]
procedure OnBeforeCalculateBonus(var CustomerNo: Code[20]; var BonusAmount: Decimal)
begin
end;
procedure CalculateBonus(CustomerNo: Code[20]): Decimal
var
BonusAmount: Decimal;
begin
OnBeforeCalculateBonus(CustomerNo, BonusAmount);
// Calculation logic...
exit(BonusAmount);
end;
}SingleInstance Codeunits
For stateful event handling across a session:
codeunit 50111 "Session State Manager"
{
SingleInstance = true;
var
IsProcessing: Boolean;
procedure SetProcessing(Value: Boolean)
begin
IsProcessing := Value;
end;
procedure IsCurrentlyProcessing(): Boolean
begin
exit(IsProcessing);
end;
}Best Practices
- Keep subscribers small — Delegate complex logic to separate procedures
- Don't modify base app code — Always use events for customization
- Test upgrade compatibility — Events are stable; triggers on base tables are not
- Use IntegrationEvent for your own extensibility points
- Avoid infinite loops — Be careful with Modify/Insert inside OnAfter events
RunTrigger Parameter
Check RunTrigger before executing subscriber logic. When false, the trigger was called internally and your subscriber should typically skip processing.
Key Revision Points (MB-820)
- Event subscribers are the upgrade-safe customization pattern
- OnBefore events can prevent operations; OnAfter events react to completed operations
- IntegrationEvent lets you build extensible apps
- SingleInstance codeunits maintain state across a user session