← All postsDevelopment ยท 3 November 2026

Coding patterns in AL: why use fifty lines when three will do

A coding pattern is a known, named way of solving a problem that comes up again and again. Business Central has a set of them, Microsoft uses them throughout the base application, and most of the long, fragile code in per-tenant extensions is a pattern that was not recognised. What patterns are, the ones every AL developer should know, each with the fifty-line version and the three-line version, and how to build your own reusable ones.

Here is a procedure we found in an inherited extension last year. It took a customer number, looked up the customer, checked whether it existed, read the credit limit, looked up the customer's ledger entries, looped over them adding up the remaining amounts, compared the total to the limit, and returned a Boolean. Fifty-two lines, two bugs, and a loop over the ledger for every call. The base application already had the answer: Customer.CalcFields("Balance (LCY)") and a comparison. Three lines, no bugs, one SQL statement.

That is what a coding pattern is for. This post is about what patterns are, the ones that matter most in AL, and how to recognise when you are about to write fifty lines that someone has already written in three.

What a coding pattern is

A pattern is a named, reusable solution to a problem that recurs. Not a library you call, but a shape you recognise: "this is a setup table, so it has one record, a Get that inserts if missing, and a GetRecordOnce". Once you know the shape, you write it the same way every time, other developers recognise it on sight, and the platform's own tools (events, interfaces, the library codeunits) fit it. Microsoft's base application is built from a few dozen patterns, and they are documented: the Business Central design patterns wiki from the NAV era, the AL guidelines in the Microsoft Learn documentation, and, increasingly, the base application's own source, which is the best pattern catalogue there is because it is the one that survives every release.

The alternative to patterns is not "no patterns". It is each developer inventing their own shape for the same problem, so that an extension with three authors has three ways of reading a setup record, three ways of building a description, and three ways of calling an API, none of them tested the same way. Fifty lines where three would do is usually a sign that a pattern was not recognised, and the cost is not the forty-seven lines. It is that the fifty lines have bugs the three do not, perform worse, and will be maintained by someone who has to read all fifty to find out what they do.

The patterns that matter most in AL

1. Let the platform do the sum

Fifty lines: loop over ledger entries adding up amounts. Three lines: a FlowField with a SIFT key, read with CalcFields, or CalcSums over a filtered record. The platform does it in one query, the result is right for every filter, and it is how the base application computes every balance on every card.

Customer.SetAutoCalcFields("Balance (LCY)");
Customer.Get(CustomerNo);
exit(Customer."Balance (LCY)" > Customer."Credit Limit (LCY)");

2. Singleton setup: GetRecordOnce

Every extension has a setup table with one record. Fifty lines: every procedure does Setup.Get() with an if not ... then Setup.Insert(), and reads it again on every call in a loop. The pattern: one procedure on the setup table, called everywhere, which reads once per session and caches:

procedure GetRecordOnce()
begin
    if IsRecordRead then
        exit;
    if not Get() then
        Insert();
    IsRecordRead := true;
end;

Microsoft's own setup tables (General Ledger Setup, Sales & Receivables Setup) use exactly this; copy it.

3. Events instead of copies

Fifty lines: a copy of the base posting codeunit with your change in the middle. The pattern: a thin subscriber to the published event, calling your own codeunit. Covered in full in the events post; it is the pattern that makes the rest of extension development possible.

4. Interface per enum value (the strategy pattern)

Fifty lines: a case statement on a "type" field with a branch per carrier, repeated in the send procedure, the label procedure and the tracking procedure. The pattern: an interface declaring Send, PrintLabel and Track, an enum whose values each implement it, and the calling code that never knows which one it has:

procedure Send(Shipment: Record "CON Courier Shipment")
var
    Carrier: Interface "CON ICarrier";
begin
    Carrier := Shipment.Carrier;   // enum value resolves to its implementation
    Carrier.Send(Shipment);
end;

Adding a carrier is a new enum value and a new codeunit; no existing code changes. Another app can add one too. The base application uses this for price calculation, document sending and much else.

5. Facade and library codeunits

Fifty lines: every place that sends an email builds the message, finds the account, handles the error. The pattern: use the System Application's facades (Email, Email Message, Base64 Convert, Temp Blob, Data Compression, Http, Json helpers, Azure Key Vault, Upgrade Tag, Feature Key, Retention Policy) and the business library codeunits (No. Series, Document Attachment, Item Availability Management, Dimension Management, Gen. Jnl.-Post Line). Before writing a utility, search the System Application for it; the odds are high it exists, is tested, and is maintained by Microsoft.

6. Temporary tables and dictionaries as working storage

Fifty lines: nested loops doing a Get inside a FindSet, or writing to a real table to hold intermediate results. The pattern: read what you need once into a temporary record (Record Item temporary) or a Dictionary of [Code[20], Decimal], work in memory, write the result. Faster, transaction-safe, and testable.

7. Posting through journals, never ledgers

Fifty lines: inserting G/L entries, item ledger entries or value entries directly. The pattern: build a journal line (Gen. Journal Line, Item Journal Line) and call the posting codeunit (Gen. Jnl.-Post Line, Item Jnl.-Post Line). The ledgers are written by exactly one piece of code each, which is why they reconcile; your extension joins that queue rather than bypassing it.

8. The Try/Error boundary pattern

Fifty lines: an HTTP call wrapped in nested ifs checking every status. The pattern: a [TryFunction] that makes the call and errors on anything unexpected, a caller that logs telemetry and decides what to do with false, and an ErrorInfo with an action for the user. See error handling.

9. Lookups and validation through TableRelation

Fifty lines: an OnLookup trigger that opens a list page, and an OnValidate that checks the value exists. The pattern: TableRelation = Customer; on the field. The lookup, the validation, the drill-down and the "does not exist" error all come free, in the standard form, in every client.

10. Install and upgrade with tags

Fifty lines: upgrade code that checks a version number and hopes it runs once. The pattern: the Upgrade Tag facade; one procedure per data change, guarded by its tag, registered on install. See changing a live extension.

Reusable patterns of your own

The ten above are the platform's. A mature extension also has its own: the way it builds a courier payload, the way it maps a web order, the way it writes telemetry. Those become reusable patterns when:

A per-tenant extension that follows this has perhaps a dozen patterns of its own. New work is "which pattern is this?", and the answer is usually three lines calling something that already exists.

How to recognise you are writing the fifty-line version

Each of those is the moment to stop and look: in the System Application, in the base application's own code for the same problem, in your own repository. Nine times out of ten the three-line version is already there. The tenth time, you write it once, properly, and it becomes the pattern. The performance and naming posts cover the two things that make a pattern easy to find again: it is fast, and it is called what it does.

Talk to us
Want this done in your Business Central?
Book a 30 minute callGet the monthly newsletter

More from the blog