Erik's ToolBox Documentation
Index

AL Language Overview

Introduction

Erik's ToolBox implements a subset of the standard AL language used in Business Central. Unlike the standard development workflow, ToolBox compiles and interprets AL code at runtime. You do not need VS Code, a development environment, or app deployment to write and execute AL scripts.

This makes ToolBox ideal for quick automation, data manipulation, and administrative tasks directly within Business Central.

Program Structure

Every ToolBox script follows a straightforward structure consisting of three sections, in this order:

  1. Variables - Data storage declared in a var block
  2. Procedures - Reusable blocks of logic
  3. Main body - The begin/end block that serves as the script entry point

A script starts directly with a var section (if there are variables) or begin (if there are no variables or procedures). When you execute a script, ToolBox runs the statements in the main begin/end block.

Basic Program Structure

var
    Greeting: Text;
begin
    Greeting := 'Hello from ToolBox!';
    Message(Greeting);
end;

This is the simplest possible ToolBox script. When you run the script, ToolBox executes the statements between begin and end.

Program with Procedures

var
    OriginalPrice: Decimal;

procedure CalculateDiscount(Amount: Decimal; DiscountPct: Decimal): Decimal
begin
    exit(Amount * DiscountPct / 100);
end;

begin
    OriginalPrice := 250.00;
    Message('Discount: %1', CalculateDiscount(OriginalPrice, 10));
end;

When the script includes procedures, they are defined between the var block and the main begin/end block.

Data Types

ToolBox supports the following data types in your scripts.

Simple Types

Type Description
Integer Whole numbers (32-bit)
BigInteger Whole numbers with a much larger range (64-bit)
Decimal Numbers with decimal places
Text Variable-length string
Code Uppercase, trimmed string
Boolean True or False
Date Calendar date (native value)
Time Time of day (native value)
DateTime Combined date and time (native value)
Guid Globally unique identifier
Option Enumerated value, declared with its members: Status: Option Open,Released,Closed;
Duration Time span in milliseconds
DateFormula Relative date calculation formula
Char A single character (also usable as its integer code)
Byte A single byte value (0..255)

Text and Code accept an optional maximum length: Name: Text[100];, ItemNo: Code[20];. Assigning a longer value to a sized variable raises an overflow error rather than truncating, so use CopyStr when the source may be longer.

Dates, times, and datetimes are stored as native values, so they compare chronologically and support arithmetic (for example Date - Date yields a number of days). ToolBox also converts automatically between compatible types on assignment and comparison. See Types and Automatic Conversion.

Complex Types

Type Description
Record Database table reference
Dialog Progress dialog
array[N] of Type Fixed-size, 1-based array
List of [Type] Dynamic, 1-based list
Dictionary of [Key, Value] Key/value dictionary
JsonObject / JsonArray / JsonToken / JsonValue JSON values
InStream / OutStream Streams for reading/writing data
Codeunit Subtype A supported standard codeunit

Types that exist in standard AL but cannot be declared in a ToolBox script include TextBuilder, Label, Enum, Interface, Variant, RecordRef, FieldRef, HttpClient, XmlDocument, Blob, and Page/Report/Query variables. See Limitations and Differences from Standard AL for the full list and the replacements to use.

Variable Declarations

You declare variables in a var block. Variables can be declared at the script level (before begin) or at the local level (inside a procedure).

Script Variables

Variables declared in the script's var block are accessible from the main body and from all procedures in the script.

var
    Counter: Integer;
    CustomerName: Text;
    TotalAmount: Decimal;
    IsActive: Boolean;
begin
    Counter := 10;
    CustomerName := 'Northwind Traders';
    TotalAmount := 1500.75;
    IsActive := true;

    Message('Customer: %1, Amount: %2', CustomerName, TotalAmount);
end;

Procedure Variables

Procedures can declare their own local variables in a var block. These are only accessible within that procedure.

var
    GlobalCounter: Integer;

procedure IncrementCounter()
begin
    GlobalCounter += 1;
end;

begin
    GlobalCounter := 1;
    IncrementCounter();
    Message('Counter is now: %1', GlobalCounter);
end;

Constants and Literals

You can use literal values directly in your code:

  • Integer literals: 42, -7, 0
  • Decimal literals: 3.14, -0.5, 100.00
  • Text literals: 'Hello World', 'It''s a test' (use two single quotes for an apostrophe).

A text literal cannot span lines and has no backslash escapes.

  • Boolean literals: true, false
  • Date literals: 20240101D, 0D (or use DMY2Date())
  • Time literals: 120000T, 120000.500T, 0T
  • DateTime literal: only 0DT. Build other values with CreateDateTime(Date, Time).
  • Char literals: a one-character text such as 'A', or the character code 65, wherever a

Char is expected

Identifiers, keywords, and member names are case-insensitive. Names that contain spaces or punctuation, or that collide with a keyword, are written in double quotes: "Sales Header", "No.", "Report". The reserved words are and or xor not div mod break else false true for foreach to downto procedure trigger if then exit var while do repeat until begin end case of page codeunit report run runmodal action; in and temporary are recognized by position and are not reserved.

Expressions and Operators

ToolBox supports the standard AL operators for building expressions.

Arithmetic Operators

Operator Description Example
+ Addition A + B
- Subtraction A - B
* Multiplication A * B
/ Division (decimal) A / B
div Integer division A div B
mod Modulo (remainder) A mod B

Comparison Operators

Operator Description Example
= Equal to A = B
<> Not equal to A <> B
< Less than A < B
> Greater than A > B
<= Less than or equal A <= B
>= Greater than or equal A >= B

Logical Operators

Operator Description Example
and Logical AND A and B
or Logical OR A or B
not Logical NOT not A
xor Exclusive OR A xor B
in Set membership A in [1, 2, 3]

Operator Precedence

ToolBox follows the standard AL precedence rules, which differ from C-style languages: and and xor bind as tightly as *, or binds like +, and the comparison operators bind loosest. Always put parentheses around each comparison when you combine them:

if (Amount > 1000) and (Customer.Blocked = Customer.Blocked::" ") then   // correct
if Amount > 1000 and Customer.Blocked = Customer.Blocked::" " then       // wrong: parsed as Amount > (1000 and ...)
Level (high to low) Operators
Unary -x, +x, not x (not applies only to the primary expression that follows; write not (X in [1, 2]))
Multiplicative *, /, div, mod, and, xor
Additive +, -, or
Comparison =, <>, <, <=, >, >=, in

and and or do not short-circuit: both operands are always evaluated. Use a nested if when the right-hand side must only run if the left-hand side is true.

Ternary Operator

ToolBox adds a conditional expression that standard AL does not have:

Label := (Amount > 1000) ? 'Large' : 'Small';

Compound Assignment

+=, -=, *=, and /= work on variables, on record fields (Customer."Credit Limit (LCY)" += 100), and on array elements (Totals[i] += Amount).

String Concatenation

You use the + operator to concatenate text values:

var
    FirstName: Text;
    LastName: Text;
    FullName: Text;
begin
    FirstName := 'John';
    LastName := 'Smith';
    FullName := FirstName + ' ' + LastName;
    Message(FullName);
end;

String Indexing

A Text variable can be indexed like an array of characters. S[i] reads the character at 1-based position i (as a Char; positions past the end read as 0), and S[i] := 'x' replaces that character. Writing to position StrLen(S) + 1 appends one character.

var
    S: Text;
    C: Char;
begin
    S := 'hello';
    C := S[1];              // 'h'
    S[1] := 'H';            // S = 'Hello'
    S[6] := '!';            // S = 'Hello!'
end;

Comments

ToolBox supports single-line comments using // and block comments using /* ... */:

var
    Total: Decimal;
begin
    // Calculate the total with tax
    Total := 100.00;
    Total := Total * 1.25; // Add 25% VAT
    /* Multi-line comments are
       also supported */
    Message('Total with tax: %1', Total);
end;

Procedures

Procedures allow you to organize your code into reusable blocks. You can define parameters, return values, and local variables.

Declaring a Procedure

procedure CalculateDiscount(Amount: Decimal; DiscountPct: Decimal): Decimal
begin
    exit(Amount * DiscountPct / 100);
end;

Procedure with Local Variables

procedure FormatCustomerInfo(CustomerNo: Code[20]): Text
var
    Customer: Record Customer;
    Result: Text;
begin
    if Customer.Get(CustomerNo) then begin
        Result := Customer."No." + ' - ' + Customer.Name;
        if Customer.City <> '' then
            Result := Result + ' (' + Customer.City + ')';
        exit(Result);
    end;
    exit('Customer not found');
end;

Parameters and Return Types

  • Parameters are passed by value unless marked var. Records, JSON values, lists, dictionaries,

streams, and codeunits are reference types, so changes made through a parameter are visible to the caller even without var. Arrays are copied unless passed as var.

  • Parameter types can be any declarable type, including Record <Table>, `Record

    temporary, Codeunit , List of [T], Dictionary of [K, V], array[N] of T, and Option A,B,C. Sized types (Text[50], Code[20]`) are accepted.

    • A procedure can return any scalar type, Option, the four JSON types, List, or Dictionary.

    It cannot return a Record, a Codeunit, or an array; pass those out through a var parameter.

    • A procedure with a return type that ends without exit(...) returns the default value of its

    return type.

    • Procedures may call each other in any order and may be recursive.

    Calling Procedures

    Procedures are defined between the var block and the main begin/end block. The main body calls them as needed. Parameterless calls may omit the parentheses (Customer.Reset;), but writing them is recommended.

    var
        OriginalPrice: Decimal;
        FinalPrice: Decimal;
        Info: Text;
    
    procedure CalculateDiscount(Amount: Decimal; DiscountPct: Decimal): Decimal
    begin
        exit(Amount * DiscountPct / 100);
    end;
    
    procedure FormatCustomerInfo(CustomerNo: Code[20]): Text
    var
        Customer: Record Customer;
    begin
        if Customer.Get(CustomerNo) then
            exit(Customer."No." + ' - ' + Customer.Name);
        exit('Customer not found');
    end;
    
    begin
        OriginalPrice := 250.00;
        FinalPrice := OriginalPrice - CalculateDiscount(OriginalPrice, 10);
        Message('Final price after 10%% discount: %1', FinalPrice);
    
        Info := FormatCustomerInfo('10000');
        Message(Info);
    end;

    Working with Records

    Records are your primary way to interact with Business Central data. You declare a record variable by specifying the table it references.

    Declaring Record Variables

    var
        Customer: Record Customer;
        SalesHeader: Record "Sales Header";
        Item: Record Item;
    begin
        // Access records through these variables
    end;

    Add the temporary keyword after the table name to get an in-memory record whose rows never touch the database — useful for buffering, sorting, or filtering data in memory:

    var
        TempCustomer: Record Customer temporary;

    See Temporary Records for the full behavior.

    Accessing Fields

    You access record fields using the dot notation. Field names with special characters must be enclosed in double quotes.

    var
        Customer: Record Customer;
    begin
        if Customer.Get('10000') then begin
            Message('No.: %1', Customer."No.");
            Message('Name: %1', Customer.Name);
            Message('City: %1', Customer.City);
            Message('Balance: %1', Customer."Balance (LCY)");
        end;
    end;

    Simple Record Iteration

    var
        Customer: Record Customer;
        CustomerCount: Integer;
    begin
        Customer.SetRange("Country/Region Code", 'US');
        if Customer.FindSet() then
            repeat
                CustomerCount += 1;
                Message('Customer: %1 - %2', Customer."No.", Customer.Name);
            until Customer.Next() = 0;
    
        Message('Total US customers: %1', CustomerCount);
    end;

    Variable Scope

    Understanding variable scope is important for writing correct scripts.

    • Script variables are declared in the script's var block before begin. They persist

    for the entire script execution and are accessible from all procedures.

    • Procedure variables are declared inside a procedure's var block. They are created

    when the procedure starts and destroyed when it ends.

    If a procedure declares a local variable with the same name as a script variable, the local variable takes precedence within that procedure.

    var
        Status: Text;
    
    procedure ShowLocalStatus()
    var
        Status: Text;
    begin
        // This is a local Status variable, separate from the script-level one
        Message(Status); // Shows '' (empty, because local was never assigned)
    end;
    
    begin
        Status := 'Script value';
        Message(Status); // Shows 'Script value'
        ShowLocalStatus();
    end;

    Summary

    The AL language in ToolBox gives you a powerful runtime-interpreted scripting environment that follows the familiar syntax and conventions of standard Business Central AL. You can declare variables, write procedures, interact with records, and build complete automation scripts without deploying an app or opening VS Code.

    For more advanced topics, see the related documentation below.