NavWithNav

The premier knowledge-sharing hub for Microsoft Dynamics 365 Business Central developers, architects, and ERP professionals.

Back to Series
Development

OnAfterGetRecord vs OnAfterGetCurrRecord in Business Central AL — Know the Difference

Many AL developers unknowingly hurt list page performance by putting logic inside OnAfterGetRecord that fires for every rendered row. In this tip, we break down the key difference between the two triggers, when to use each, and a real code example that keeps your list pages blazing fast.

NitinMarch 30, 2026 5 min read
["[\"Business Central\"""\"AL Development\"""\"Dynamics 365\"""\"BC Performance\"""\"List Pages\"""\"AL Tips\"""\"ERP Development\"""\"OnAfterGetRecord\"""\"BC Tips\"""\"Microsoft Dynamics\"]"]
OnAfterGetRecord vs OnAfterGetCurrRecord in Business Central AL — Know the Difference

If you've been developing extensions in Business Central AL for any length of time, you've almost certainly written code in the OnAfterGetRecord trigger on a list page. It feels natural — the record is loaded, so that's where you put your logic, right?

Wrong — and this single misunderstanding is behind some of the worst performance issues we see on real customer environments. Slow list scrolling, sluggish page loads, excessive SQL round trips — a surprising number of them trace directly back to this mistake.

In this post, we're going to break down the exact difference between OnAfterGetRecord and OnAfterGetCurrRecord, show you when to use each, and walk through real AL code examples that demonstrate the right approach.

Understanding the Triggers

OnAfterGetRecord

The OnAfterGetRecord trigger fires for every single record that the platform renders into the list view. This means if a user scrolls through 200 rows, this trigger fires 200 times. If they sort the list, it fires again. If they resize the browser window and the grid re-renders additional rows, it fires again.

The intended purpose of this trigger is lightweight, row-level operations — specifically things like computing a StyleExpr variable that controls how a row is visually formatted (bold, unfavorable, strong, etc.). That's it. That's the sweet spot.

 

OnAfterGetCurrRecord

The OnAfterGetCurrRecord trigger fires only when the user changes their selection — i.e., moves focus from one row to another. It fires once per selection change, and it fires on the currently highlighted record.

This makes it the safe, correct home for any logic that involves looking up related records, computing page-level variables, refreshing FactBoxes, or setting action visibility based on the current record's state.


Side-by-Side Comparison

 

Aspect

OnAfterGetRecord

OnAfterGetCurrRecord

Fires on

Every rendered row

Selected row only

Frequency

N times per scroll

Once per row focus

Use for

StyleExpr, row-level format

FactBoxes, actions, vars

Avoid

GET, FIND, CALCFIELDS

Nothing — safe here

Performance risk

High — SQL per row

Low — single call

The Problem in Practice

Here is the pattern we see far too often in the wild. A developer needs to show a count of related Sales Lines next to each Sales Header in a list. They write this:

 

❌ Wrong approach

This code fires a database GET for every row rendered — causing N+1 SQL queries.

AL (Business Central)
// ❌ BAD: OnAfterGetRecord with expensive database call
trigger OnAfterGetRecord()
begin
    // This fires for EVERY row the list renders into view
    SalesLine.Reset();
    SalesLine.SetRange("Document Type", Rec."Document Type");
    SalesLine.SetRange("Document No.", Rec."No.");
    LineCount := SalesLine.Count();
end;


On a list with 50 visible rows, this fires 50 separate SETRANGE + COUNT queries. On a slow VPN or a cloud tenant under load, this is immediately visible to the end user as a laggy, janky scroll experience.


The Correct Approach

Move expensive logic to OnAfterGetCurrRecord

If the logic only needs to run for the selected row — for example, to update a FactBox or populate a page variable shown in a summary tile — move it entirely to OnAfterGetCurrRecord:

 

✅ Correct approach

OnAfterGetCurrRecord fires once per user selection — not once per rendered row.


AL (Business Central)
// ✅ GOOD: OnAfterGetCurrRecord for selection-dependent logic
trigger OnAfterGetCurrRecord()
begin
    // Fires only when the user clicks a different row
    SalesLine.Reset();
    SalesLine.SetRange("Document Type", Rec."Document Type");
    SalesLine.SetRange("Document No.", Rec."No.");
    LineCount := SalesLine.Count();
 
    // Update FactBox or page-level variable safely here
    CurrPage.SalesLineFactbox.Page.SetRecord(Rec);
end;



Keep OnAfterGetRecord minimal

If you genuinely need per-row logic — most commonly for row styling — keep OnAfterGetRecord fast and free of any database calls:

AL (Business Central)
// ✅ GOOD: OnAfterGetRecord for lightweight StyleExpr only
var
    StyleExpr: Text;
 
trigger OnAfterGetRecord()
begin
    // Only compute visual style — no DB calls
    case Rec.Status of
        Rec.Status::Blocked:
            StyleExpr := 'Unfavorable';
        Rec.Status::Released:
            StyleExpr := 'Favorable';
        else
            StyleExpr := '';
    end;
end;


The StyleExpr field is then referenced in the field's Style property on the page:

AL (Business Central)
field("No."; Rec."No.")
{
    ApplicationArea = All;
    Style = StyleExpr;  // References the computed variable
    StyleExpr = StyleExpr;
}


Found this useful?

If this tip helped you catch a performance issue in your BC extension, share it with your team. Subscribe to the blog for more AL development tips covering performance, architecture, AppSource readiness, and real-world patterns.


0
0

Discussion (0)

Leave a comment

No comments yet. Be the first to share your thoughts!

Nitin Verma

Nitin Verma

Solution Architect

Extensive experience specializing in Microsoft Dynamics NAV, Business Central, Power Platform, and ERP Architecture.

Read full bio

Search Articles

Monthly Archive

Loading...

Visitor Stats