Events are how an extension changes what Business Central does without changing Business Central. The three kinds of event, how subscribers bind, the IsHandled pattern that lets you replace standard behaviour cleanly, what to do when the event you need does not exist, and the mistakes that make event code slow or fragile.
Everything an extension does to the base application, it does through events. There is no other supported way to run your code inside Microsoft's posting routine, validation or page action. Understanding events properly is therefore most of understanding AL extension development, and the patterns are small enough to fit in one post.
OnBeforeInsertEvent, OnAfterValidateEvent for each field, and so on) and page trigger. You subscribe to them without Microsoft having to publish anything. Use them for "when this field changes" and "before this record is deleted".[IntegrationEvent], at the points Microsoft decided extensions would need: OnBeforePostSalesDoc, OnAfterCopyGenJnlLineFromSalesHeader, hundreds of others. They are the supported seam into business logic and they carry the parameters you need (usually the record, often by var, often an IsHandled).[BusinessEvent]) are the stable, documented subset meant as a public contract for ISVs and external systems; they change less often. Externally, they also surface as Power Automate triggers.Your own codeunits publish events too, with the same attributes, so that other apps (or your future self) can extend your extension the same way.
codeunit 50120 "CON Sales Event Subscribers"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnBeforePostSalesDoc, '', false, false)]
local procedure OnBeforePostSalesDoc(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean)
var
PalletCheck: Codeunit "CON Pallet Check";
begin
PalletCheck.ValidateBeforePost(SalesHeader);
end;
}
The attribute names the object, the event and the element (the field name for a validate event, blank otherwise), then two booleans: SkipOnMissingLicense and SkipOnMissingPermission. The subscriber's signature must match the publisher's parameters by name and type; VS Code will generate it for you from the event's definition (Ctrl+Space in the attribute).
By default a subscriber codeunit binds statically: it is active as soon as the app is installed, for every session. EventSubscriberInstance = Manual lets you bind an instance explicitly with BindSubscription, which is the pattern for subscribers that should only be live during a specific operation (and for tests).
Most OnBefore integration events carry var IsHandled: Boolean. Set it to true and the base application skips its own implementation of that step; your subscriber has handled it. This is how you replace standard behaviour rather than add to it: a different credit check, a different way of finding the price, a different document number.
The rules: check IsHandled on entry and return if another subscriber already set it, so two apps do not both replace the same step; set it only when you have genuinely done the whole job; and prefer not to use it at all when an OnAfter would do, because replacing standard logic means you now own that logic through every release wave.
Look again: the base application has thousands and the one you need is often a few lines from where you were looking. Check whether a trigger event on the table will do. If there is genuinely nothing, request it on the AL extensibility GitHub (microsoft/ALAppExtensions issues, "Event request" template) with the codeunit, the line and the signature; Microsoft adds most reasonable requests within one or two releases. What you do not do is copy the base procedure into your own codeunit and call that instead, or try to reach the behaviour by subscribing to something unrelated nearby. Both work until the next update.
OnAfterValidateEvent on Sales Line Quantity fires on every line of every order, including during posting and copy document. Keep it to a check and a flag; do the work later.Get inside a subscriber on Item Ledger Entry's insert event runs once per ledger entry. Cache, or move the logic to an event that fires once per document."CON Event Subscribers" with four hundred procedures. Split by area; a subscriber codeunit per base codeunit you extend is a reasonable rule.Commit.OnAfterPostSalesDoc fires, the document is posted; an error there leaves the user with a posted document and a red dialog. Validate in OnBefore.Because the subscriber is thin and the logic is in a codeunit, test the codeunit directly. Then one integration test that actually posts a document and checks the effect, to prove the event fires and the signature still matches. When Microsoft changes an event's parameters (it happens, with an obsolete period), that test is what tells you, in the NextMajor build, before your customer does. The design rules and pipeline posts cover the rest.