In this video, I show an old favorite, the Excel Buffer. A great way to get data into Business Central. I create a few handy extra functions to make the experience nicer.

In this video, Erik demonstrates how to import data from an Excel file into Business Central using the Excel Buffer table in AL. He walks through the entire process — from understanding what the Excel Buffer is, to uploading a file via streams, reading sheet data into a temporary table, and extracting cell values into a custom table using clean, readable helper functions.
What Is the Excel Buffer?
The Excel Buffer is a table in the Business Central Base Application. You might expect it to live in the System Application (since it’s more of a technical utility than a business object), and Erik notes it will likely move there in the future. But for now, it resides in the Base App.
The Excel Buffer table has fields for Row No., Column No., Column ID, Cell Value as Text, and more. Essentially, each cell in an Excel spreadsheet becomes a single record in the Excel Buffer. The table is not declared as temporary by default, but you should always use it as a temporary table. This is critical — it’s expected that the table will eventually get the TableType = Temporary property.
Setting Up the Target Table and Page
Before importing, Erik created a simple table with four fields to match the four columns in the Excel spreadsheet, along with a page to display the imported data:
table 50100 "Excel Test"
{
Caption = 'Excel Test';
DataClassification = ToBeClassified;
fields
{
field(1; Primary; Code[10])
{
Caption = 'Primary';
DataClassification = SystemMetadata;
}
field(2; "Text Data"; Text[100])
{
Caption = 'Text Data';
DataClassification = SystemMetadata;
}
field(3; "Date Data"; Date)
{
Caption = 'Date Data';
DataClassification = SystemMetadata;
}
field(4; "Decimal Data"; Decimal)
{
Caption = 'Decimal Data';
DataClassification = SystemMetadata;
}
}
keys
{
key(PK; Primary)
{
Clustered = true;
}
}
}
Opening an Excel File via Streams
The Excel Buffer has several Open functions: OpenBook, OpenBookForUpdate, OpenBookStream, OpenExcelWithName, and more. Some of these — like OpenExcelWithName — are scoped for on-premises only because they accept a file path as a parameter, which doesn’t work in a SaaS/extension context.
For extension development (which is essentially all modern Business Central development), you need to use OpenBookStream. This function accepts an InStream and a sheet name. To get the InStream, use the UploadIntoStream function, which prompts the user to select a file and connects the resulting data to a stream variable.
if UploadIntoStream('Gimmi the Excel', '', '', Filename, InS) then begin
Buffer.OpenBookStream(InS, 'Sheet1');
Buffer.ReadSheet();
// ...
end;
Note that the second and third parameters of UploadIntoStream (from folder and from filter) are legacy parameters from the Windows client and are no longer used. The Filename variable is passed by reference and returns the name of the selected file.
Understanding the Buffer Data
After calling ReadSheet(), each cell with a value becomes a record in the temporary Excel Buffer table. In Erik’s example spreadsheet, there were four columns and six data rows (plus a header area), totaling 24 populated cells. An empty cell does not produce a record — this is important to understand and handle correctly.
You can navigate the buffer using standard Business Central record operations — FindLast, SetRange, Reset, and Get. For example, to find the last row of data, you can filter on a specific column and call FindLast():
Buffer.SetRange("Column No.", 4);
Buffer.FindLast();
LastRow := Buffer."Row No.";
Buffer.Reset();
Building Readable Helper Functions
Rather than scattering Buffer.Get() calls and evaluations throughout the import logic, Erik creates a utility codeunit with clean helper functions. This approach makes the import code much more readable and maintainable:
codeunit 50100 "Excel Tools"
{
procedure GetText(var Buffer: Record "Excel Buffer" temporary; Col: Text; Row: Integer): Text
begin
if Buffer.Get(Row, GetColumnNumber(Col)) then
exit(Buffer."Cell Value as Text");
end;
procedure GetDate(var Buffer: Record "Excel Buffer" temporary; Col: Text; Row: Integer): Date
var
d: Date;
begin
if Buffer.Get(Row, GetColumnNumber(Col)) then begin
Evaluate(D, Buffer."Cell Value as Text");
exit(D);
end;
end;
procedure GetDecimal(var Buffer: Record "Excel Buffer" temporary; Col: Text; Row: Integer): Decimal
var
d: Decimal;
begin
if Buffer.Get(Row, GetColumnNumber(Col)) then begin
Evaluate(d, Buffer."Cell Value as Text");
exit(d);
end;
end;
procedure GetColumnNumber(ColumnName: Text): Integer
var
columnIndex: Integer;
factor: Integer;
pos: Integer;
begin
factor := 1;
for pos := StrLen(ColumnName) downto 1 do
if ColumnName[pos] >= 65 then begin
columnIndex += factor * ((ColumnName[pos] - 65) + 1);
factor *= 26;
end;
exit(columnIndex);
end;
procedure GetColumnName(columnNumber: Integer): Text
var
dividend: Integer;
columnName: Text;
modulo: Integer;
c: Char;
begin
dividend := columnNumber;
while (dividend > 0) do begin
modulo := (dividend - 1) mod 26;
c := 65 + modulo;
columnName := Format(c) + columnName;
dividend := (dividend - modulo) DIV 26;
end;
exit(columnName);
end;
}
Key Design Decisions
- Pass by reference (
var): The Buffer parameter is passed by reference. This is critical when working with temporary tables — if you pass by value, you end up with two separate copies of the temporary data, which leads to confusing bugs whereGetcalls find nothing. - Implicit default returns: Notice that the
Getcall is wrapped in anifstatement. If the cell doesn’t exist (empty cell), the function simply exits without returning a value. In AL, this means it returns the default for the type — empty string for Text, 0 for Decimal, 0D for Date. This gracefully handles missing cells. - Column letters instead of numbers: The
GetColumnNumberfunction converts Excel-style column letters (A, B, …, Z, AA, AB, …) to column numbers. It iterates from right to left through the column name, treating each character as a base-26 digit where A=1, B=2, …, Z=26. Each position to the left increases the factor by 26.
The Complete Import Logic
With the helper functions in place, the import action becomes clean and easy to read:
trigger OnAction()
var
x: Codeunit "Excel Tools";
Buffer: Record "Excel Buffer" temporary;
Data: Record "Excel Test";
InS: InStream;
Filename: Text;
Row: Integer;
LastRow: Integer;
begin
Data.DeleteAll();
if UploadIntoStream('Gimmi the Excel', '', '', Filename, InS) then begin
Buffer.OpenBookStream(InS, 'Sheet1');
Buffer.ReadSheet();
Buffer.SetRange("Column No.", 4);
Buffer.FindLast();
LastRow := Buffer."Row No.";
Buffer.Reset();
for Row := 9 to LastRow do begin
Data.Init();
Data.Primary := x.GetText(Buffer, 'D', Row);
Data."Text Data" := x.GetText(Buffer, 'E', Row);
Data."Date Data" := x.GetDate(Buffer, 'F', Row);
Data."Decimal Data" := x.GetDecimal(Buffer, 'G', Row);
Data.Insert();
end;
end;
end;
Reading this code, you can clearly see: “For each row starting at row 9, get column D as the primary key, column E as text data, column F as a date, and column G as a decimal.” The cell references (D9, E9, F9, G9, etc.) map directly to the Excel spreadsheet, making the code intuitive to follow.
A Note on Evaluate and Regional Settings
Erik briefly touches on the Evaluate function’s optional third parameter. You can pass 9 to force evaluation using ISO 8601 format, which makes your code more resilient across different regional settings. However, this only works if the data in the Excel file is actually formatted in ISO format. If the dates in your spreadsheet are formatted according to a specific regional setting, the standard Evaluate (without the format parameter) will use the current user’s regional settings, which may or may not match. Test carefully with your specific data.
Performance Considerations
Since the Excel Buffer creates one record per cell, importing very large spreadsheets can be a memory-intensive operation. For each cell you need to read, you’re performing a Get on the temporary table, which means a lot of table traffic. For small to medium-sized imports, this works beautifully. For very large datasets, you may want to consider alternatives like BCCL (Business Central Configuration Language) or other bulk import mechanisms.
Summary
The Excel Buffer is a versatile and widely-used tool for importing Excel data into Business Central. The key takeaways are:
- Always use it as temporary — declare your Excel Buffer variable with the
temporarykeyword. - Use
OpenBookStreamwithUploadIntoStreamfor SaaS-compatible file uploads. - Each cell is a record — empty cells don’t create records.
- Build helper functions that accept column letters and row numbers for clean, readable import code.
- Pass the buffer by reference (
var) to avoid duplicate temporary table issues. - Be mindful of performance with large spreadsheets due to the one-record-per-cell design.