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:

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
Recis 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, orFindLastdirectly onRecin 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.SetSelectionFilterto 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
NoImplicitWithin your app to make all references toRecexplicit, 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.