| name | skill-events |
| description | AL event-driven architecture for Business Central. Use when creating EventSubscribers, IntegrationEvents, BusinessEvents, or implementing publisher/subscriber patterns in extensions. |
Skill: AL Event-Driven Architecture
Purpose
Discover, design, and implement event-driven patterns in AL Business Central extensions: event discovery via Event Recorder, subscriber/publisher creation, IsHandled patterns, and extensibility design.
When to Load
This skill should be loaded when:
- Extending standard BC functionality (posting, validation, master data changes)
- Designing custom integration events for a new codeunit or extension
- A subscriber is not firing and needs investigation
- An architecture requires extensibility points for future consumers
- Implementing document posting interventions, workflow triggers, or UI customizations
Core Patterns
Pattern 1: Event Discovery with Event Recorder
Before writing any subscriber, discover which events are raised during the business process:
1. Open the Event Recorder in the BC client / VS Code (a human step — no agent tool here)
2. Start recording
3. Execute the business process in BC (e.g., post a sales order)
4. Stop recording
5. Analyze the event list — filter by object or keyword
6. Identify the OnBefore/OnAfter event closest to your extension point
Use al_search_objects and al_get_object_definition to inspect publisher signatures.
Use al_find_references to see who else subscribes to the same event.
Pattern 2: Event Subscriber
// Subscriber codeunit — name with "Handler" suffix
codeunit 50100 "Sales Document Events Handler"
{
// Attribute breakdown:
// ObjectType — Table, Codeunit, Page, Report, etc.
// ObjectId — Database::"Sales Header" or Codeunit::"Customer Management"
// EventName — exact name of the publisher procedure (e.g., OnBeforeInsert)
// ElementName — '' for table/codeunit events; field name for page field triggers
// SkipOnMissingLicense — false (recommended: always run)
// SkipOnMissingPermission — false (recommended: always run)
[EventSubscriber(ObjectType::Table, Database::"Sales Header",
OnBeforeInsert, '', false, false)]
local procedure OnBeforeInsertSalesHeader(
var SalesHeader: Record "Sales Header";
RunTrigger: Boolean)
begin
ValidateCustomFields(SalesHeader);
end;
}
Subscriber rules:
- Procedure MUST be
local
- Parameter signature MUST exactly match the publisher (name, type, order)
- One subscriber per event per codeunit (keep handler codeunits focused)
- Never use
Commit inside a subscriber unless absolutely necessary
Pattern 3: Integration Event Publisher with IsHandled
The standard extensibility pattern: OnBefore allows interception, OnAfter allows reaction.
codeunit 50101 "Customer Management"
{
procedure CreateCustomer(var Customer: Record Customer): Boolean
var
IsHandled: Boolean;
begin
// 1. Raise OnBefore — subscribers can validate, modify, or skip
OnBeforeCreateCustomer(Customer, IsHandled);
if IsHandled then
exit(true);
// 2. Default logic
if not Customer.Insert(true) then
exit(false);
// 3. Raise OnAfter — subscribers can react (logging, integration, etc.)
OnAfterCreateCustomer(Customer);
exit(true);
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeCreateCustomer(
var Customer: Record Customer;
var IsHandled: Boolean)
begin
// Empty body — subscribers provide the implementation
// IsHandled = true → skip default logic
end;
[IntegrationEvent(false, false)]
local procedure OnAfterCreateCustomer(var Customer: Record Customer)
begin
// Empty body — subscribers react after creation
end;
}
IntegrationEvent attribute:
IntegrationEvent(IncludeSender, GlobalVarAccess)
IncludeSender = false — recommended for clean API (no coupling to publisher internals)
GlobalVarAccess = false — recommended (prevent subscribers from accessing publisher global vars)
Pattern 4: Business Event
Business events are user-disablable — use for optional, non-critical functionality:
[BusinessEvent(IncludeSender)]
local procedure OnCustomerCreditLimitExceeded(
Customer: Record Customer;
RequestedAmount: Decimal)
begin
// Subscribers may send notification, log, or block — user can disable
end;
| Attribute | Cannot be disabled | Use for |
|---|
IntegrationEvent | Correct | Critical business logic, posting interventions |
BusinessEvent | Can be disabled by user | Optional features, external integrations, notifications |
Pattern 5: Well-Designed Event Parameters
// ✅ Good — rich context, var where modification expected, IsHandled for OnBefore
[IntegrationEvent(false, false)]
local procedure OnBeforePostDocument(
var DocumentHeader: Record "Sales Header"; // var — subscriber may modify
var DocumentLines: Record "Sales Line"; // var — subscriber may modify
PostingDate: Date; // value — read-only context
var IsHandled: Boolean) // var — subscriber may skip default
begin
end;
// ✅ Good — results context for OnAfter
[IntegrationEvent(false, false)]
local procedure OnAfterPostDocument(
DocumentHeader: Record "Sales Header"; // value — read-only (already posted)
PostedDocumentNo: Code[20]; // result reference
PostingResult: Boolean) // success/failure
begin
end;
Parameter design rules:
var Record — when subscribers should be able to modify the record
- Non-var
Record — when record is read-only context
- Always include
var IsHandled: Boolean in OnBefore events
- Include result/outcome parameters in
OnAfter events
- Use meaningful parameter names that describe business intent
Workflow
Step 1: Discover Events
- Open the Event Recorder in the BC client / VS Code (a human step — no agent tool here)
- Execute the target business process in BC
- Review recorded events — filter by relevant object
- Use
al_get_object_definition to inspect publisher signatures
- Use
al_find_references to check existing subscribers (avoid conflicts)
Step 2: Design Event Architecture
For subscribing to existing events:
- Identify the right OnBefore / OnAfter event from Step 1
- Match the parameter signature exactly
- Create a handler codeunit with naming:
"{Domain} Events Handler"
For creating new integration events in your codeunit:
- Identify logical extension points in your business logic
- Add
OnBefore event with IsHandled pattern before key operations
- Add
OnAfter event after key operations with result context
- Follow naming:
OnBefore{Action} / OnAfter{Action}
- Choose
IntegrationEvent (critical) or BusinessEvent (optional)
Step 3: Implement
Write the subscriber/publisher structures with Write/Edit (in VS Code the teventsub/tevent snippets scaffold these for a human), then fill the body:
// Subscriber handler codeunit
codeunit 50102 "Customer Validation Handler"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Customer Management",
OnBeforeCreateCustomer, '', false, false)]
local procedure ValidateOnBeforeCreate(
var Customer: Record Customer;
var IsHandled: Boolean)
begin
ValidateCustomerCreditLimit(Customer);
if ShouldSkipDefaultProcessing(Customer) then
IsHandled := true;
end;
}
Step 4: Verify
- Build:
Bash: al compile — check for compilation errors (signature mismatches)
- Set breakpoint inside subscriber
- Debug in VS Code (AL debugger, attach without publish) — a human step
- Execute the business process — confirm subscriber fires
- If subscriber does NOT fire, check:
- Signature mismatch (most common cause)
- Wrong
ObjectType / ObjectId
- Extension not published/active
- Another subscriber set
IsHandled = true before yours
- SkipOnMissingLicense / SkipOnMissingPermission attributes
Common Scenarios
| Scenario | Event Source | Pattern |
|---|
| Validate before posting | OnBeforePost* on Codeunit "Sales-Post" | Subscriber + IsHandled |
| Add fields to sales order | OnAfterInsert on Table "Sales Header" | Subscriber (set defaults) |
| External integration trigger | OnAfterPostDocument (custom) | Publisher in your codeunit |
| Custom approval workflow | OnBeforeRelease* | Subscriber + IsHandled to block |
| UI field validation | Page trigger OnAfterValidate on field | Subscriber with ElementName |
| Master data change audit | OnAfterModify on Table "Customer" | Subscriber (log changes) |
References
Constraints
- This skill covers event discovery, design, and implementation patterns — it does NOT duplicate the passive rules in
al-events.md (auto-applied to all .al files)
- Do NOT create publishers without an
OnBefore + OnAfter pair at minimum
- Do NOT use
Commit inside event subscribers unless absolutely required
- Do NOT set
IsHandled = true without clear documentation of why default logic is skipped
- Subscriber debugging (breakpoints, snapshots) → load
skill-debug.md
- Subscriber performance overhead analysis → load
skill-performance.md