What happens if you mess with Rec in AL?

What happens if you mess with the built-in Rec variable in AL? That’s what I’m exploring in this video, check it out:

https://youtu.be/uA04c6rBVTA

In this video, Erik explores a common pitfall in AL development for Business Central: what happens when you directly manipulate Rec on a page. Inspired by a question from a LinkedIn follower, he demonstrates why you should avoid modifying Rec‘s cursor position, how it can break your page, and the correct patterns to use instead — including CopyFilters and CurrPage.SetSelectionFilter.

What Is Rec?

Every page in Business Central has an implicit record variable called Rec. It represents the source table of the page. On the Customer List page, for example, Rec is the Customer table. It’s always present, always available — and always tied to the current state of the page, including which row the user’s cursor is on and what filters are applied.

The question is: when can you use it, and when should you not use it?

The Problem: Messing with Rec‘s Cursor

Erik starts by creating a page extension on the Customer List with a simple action that loops through all records using Rec:

if Rec.FindSet() then
    repeat
        // Fancy Processing
    until Rec.Next() = 0;

This compiles and runs without errors. But when he executes the action, the cursor jumps to the last record in the list. By calling FindSet() and Next() directly on Rec, you’re moving the page’s cursor through every row. When the loop finishes, Rec is pointing at the last record — and the page reflects that.

It Gets Worse in Triggers

Erik then takes it a step further by placing the same looping code inside the OnAfterGetRecord trigger. This trigger fires as the user navigates the table, so modifying Rec‘s position here causes the page to completely break — no navigation arrows, no errors trapped, just a non-functional page. Even a simple Rec.Next() call inside this trigger causes failures because it disrupts the page’s internal record iteration.

This leads to the first and most important lesson:

Never mess with Rec‘s cursor position. You will either confuse the user or break the system entirely.

The Solution: Use a Separate Record Variable

The correct approach is to create a new record variable and transfer the relevant information from Rec to it. Erik demonstrates two techniques:

Option 1: CopyFilters

If you want to process all records that match the page’s current filters, use CopyFilters:

var
    NewRec: Record Customer;
begin
    NewRec.CopyFilters(Rec);
    if NewRec.FindSet() then
        repeat
            // Fancy Processing
            NewRec."Name 2" := NewRec.Name;
            NewRec.Modify();
        until NewRec.Next() = 0;

This copies the active filters from Rec onto NewRec, so you iterate over the same set of records the user sees — but without touching Rec‘s cursor at all. After running this, the user’s selected row stays exactly where it was.

Option 2: CurrPage.SetSelectionFilter

If you want to process only the records the user has explicitly selected (supporting multi-select), use CurrPage.SetSelectionFilter:

var
    NewRec: Record Customer;
begin
    CurrPage.SetSelectionFilter(NewRec);
    if NewRec.FindSet() then
        repeat
            // Fancy Processing
            NewRec."Name 2" := NewRec.Name;
            NewRec.Modify();
        until NewRec.Next() = 0;

This function takes the records the user has selected on the page (marked via Ctrl+Click or similar) and sets a filter on NewRec to include only those records. If only a single record is selected, you get a direct filter on that record’s primary key. If multiple records are selected, you get a “Marked = Yes” filter.

Erik demonstrates this behavior by selecting several customers and displaying the resulting filter and count:

Message('%1 count=%2', NewRec.GetFilters(), NewRec.Count());

Selecting one record shows a filter like No.=30000. Selecting four records shows @Marked=Yes with a count of 4.

The Final Source Code

Here is the complete, clean implementation that Erik arrives at:

namespace DefaultPublisher.MessWithRec;

using Microsoft.Sales.Customer;

pageextension 50100 CustomerListExt extends "Customer List"
{
    actions
    {
        addfirst(processing)
        {
            action(Test)
            {
                Caption = 'Youtube Test';
                ApplicationArea = all;
                trigger OnAction()
                var
                    NewRec: Record Customer;
                begin
                    //NewRec.CopyFilters(Rec);
                    CurrPage.SetSelectionFilter(NewRec);
                    Message('%1 count=%2', NewRec.GetFilters(), NewRec.Count());
                    if NewRec.FindSet() then
                        repeat
                            // Fancy Processing
                            NewRec."Name 2" := NewRec.Name;
                            NewRec.Modify();
                        until NewRec.Next() = 0;
                end;
            }
        }
    }
}

Note the NoImplicitWith feature flag in app.json, which enforces explicit use of Rec. prefixes and helps prevent accidental misuse:

"features": [
    "NoImplicitWith"
]

Key Takeaways

  • Rec is tied to the page. It represents the current row and filter state. Changing its cursor position changes what the user sees — or breaks the page entirely.
  • Never call FindSet, Next, or FindLast directly on Rec in action code or navigation triggers. It will move the cursor or crash the page.
  • Use CopyFilters(Rec) to transfer the page’s current filters to a new record variable for batch processing.
  • Use CurrPage.SetSelectionFilter to work with only the records the user has explicitly selected, supporting multi-select scenarios.
  • Read from Rec, don’t write to it. Extracting filter information and selection state is perfectly safe — just don’t move the cursor or alter the record’s position in the dataset.
  • Enable NoImplicitWith in your app to make all references to Rec explicit, reducing the risk of accidental manipulation.

In short: Rec is awesome exactly the way it is. Get the information you need from it, use that information in your own variables, and leave Rec alone.