Text Methods
Introduction
In addition to the built-in string functions (such as StrLen, CopyStr, and StrPos), ToolBox supports instance methods that you call directly on text variables. These methods use dot notation (e.g., MyText.ToLower()) and provide a modern, readable syntax for string manipulation.
This page documents all text instance methods available in ToolBox. Each entry includes the syntax, description, return value, and a practical example.
Where Text Methods Can Be Called
The interpreter hands text methods to the native Business Central Text implementation, so they behave exactly like standard AL. Two rules decide where you can write them:
- The receiver must be a
Textvariable (a global, a local, or a procedure parameter), or
the Text result of a previous call in a dotted chain, such as Obj.GetText('name').ToUpper().
- Positions are 1-based.
IndexOfreturns the 1-based position of the match and0when
nothing is found. Substring, Remove, IndexOf, and LastIndexOf all count from 1, exactly like CopyStr and StrPos.
The following receivers are not supported. Copy the value into a Text variable first.
| Not supported | Write instead |
|---|---|
A string literal: 'a,b,c'.Split(',') |
Csv := 'a,b,c'; Parts := Csv.Split(','); |
A Code variable: ItemNo.ToLower() |
Txt := ItemNo; Txt := Txt.ToLower(); |
A record field: Customer.Name.ToUpper() |
Name := Customer.Name; Name := Name.ToUpper(); |
Differs from standard AL: Standard AL allows text methods on
Codevalues and on record fields. ToolBox requires aTextvariable (or chainedTextresult) as the receiver.
Case Conversion
ToLower
text.ToLower(): Text
Returns a new text value with all characters converted to lowercase. The original variable is not modified.
Returns: Text - The lowercase version of the string.
var
Input: Text;
Result: Text;
begin
Input := 'Hello World';
Result := Input.ToLower();
Message(Result); // hello world
end;
ToUpper
text.ToUpper(): Text
Returns a new text value with all characters converted to uppercase. The original variable is not modified.
Returns: Text - The uppercase version of the string.
var
Input: Text;
Result: Text;
begin
Input := 'Hello World';
Result := Input.ToUpper();
Message(Result); // HELLO WORLD
end;
Search Methods
Contains
text.Contains(substring: Text): Boolean
Checks whether the text contains the specified substring. The comparison is case-sensitive.
Returns: Boolean - true if the substring is found, false otherwise.
| Parameter | Type | Description |
|---|---|---|
| substring | Text | The text to search for |
var
Email: Text;
begin
Email := 'user@example.com';
if Email.Contains('@') then
Message('Valid email format')
else
Message('Missing @ symbol');
end;
IndexOf
text.IndexOf(substring: Text[, startIndex: Integer]): Integer
Returns the 1-based position of the first occurrence of the specified substring. Returns 0 if the substring is not found. When startIndex is given, the search starts at that position.
Returns: Integer - The 1-based position, or 0 if not found.
| Parameter | Type | Description |
|---|---|---|
| substring | Text | The text to search for |
| startIndex | Integer | Optional. 1-based position to start searching |
var
FullPath: Text;
DotPos: Integer;
begin
FullPath := 'document.xlsx';
DotPos := FullPath.IndexOf('.');
if DotPos > 0 then
Message('Extension starts at position: %1', DotPos + 1); // 10
end;
IndexOfAny
text.IndexOfAny(anyOf: Text[, startIndex: Integer]): Integer
Returns the 1-based position of the first character in the text that appears anywhere in anyOf. Returns 0 if none of the characters is found.
Returns: Integer - The 1-based position, or 0 if not found.
| Parameter | Type | Description |
|---|---|---|
| anyOf | Text | The set of characters to look for |
| startIndex | Integer | Optional. 1-based position to start searching |
var
Line: Text;
SepPos: Integer;
begin
Line := 'Name;City,Country';
SepPos := Line.IndexOfAny(',;');
Message('First separator at %1', SepPos); // 5
end;
LastIndexOf
text.LastIndexOf(substring: Text[, startIndex: Integer]): Integer
Returns the 1-based position of the last occurrence of the specified substring. Returns 0 if the substring is not found.
Returns: Integer - The 1-based position of the last occurrence, or 0 if not found.
| Parameter | Type | Description |
|---|---|---|
| substring | Text | The text to search for |
| startIndex | Integer | Optional. 1-based position to search backwards from |
var
FilePath: Text;
LastSlash: Integer;
begin
FilePath := 'C:\Users\Documents\report.pdf';
LastSlash := FilePath.LastIndexOf('\');
if LastSlash > 0 then
Message('File name: %1', FilePath.Substring(LastSlash + 1)); // report.pdf
end;
StartsWith
text.StartsWith(prefix: Text): Boolean
Checks whether the text begins with the specified prefix. The comparison is case-sensitive.
Returns: Boolean - true if the text starts with the prefix, false otherwise.
| Parameter | Type | Description |
|---|---|---|
| prefix | Text | The prefix to check |
var
DocNo: Text;
begin
DocNo := 'INV-2025-001';
if DocNo.StartsWith('INV-') then
Message('This is an invoice: %1', DocNo)
else if DocNo.StartsWith('CRM-') then
Message('This is a credit memo: %1', DocNo);
end;
EndsWith
text.EndsWith(suffix: Text): Boolean
Checks whether the text ends with the specified suffix. The comparison is case-sensitive.
Returns: Boolean - true if the text ends with the suffix, false otherwise.
| Parameter | Type | Description |
|---|---|---|
| suffix | Text | The suffix to check |
var
FileName: Text;
begin
FileName := 'export_data.csv';
if FileName.EndsWith('.csv') then
Message('CSV file detected')
else if FileName.EndsWith('.xlsx') then
Message('Excel file detected');
end;
Modification Methods
Replace
text.Replace(oldValue: Text, newValue: Text): Text
Returns a new text with all occurrences of oldValue replaced by newValue.
Returns: Text - The text with replacements applied.
| Parameter | Type | Description |
|---|---|---|
| oldValue | Text | The text to find and replace |
| newValue | Text | The replacement text |
var
PhoneNo: Text;
begin
PhoneNo := '(555) 123-4567';
PhoneNo := PhoneNo.Replace('(', '');
PhoneNo := PhoneNo.Replace(')', '');
PhoneNo := PhoneNo.Replace(' ', '');
PhoneNo := PhoneNo.Replace('-', '');
Message('Cleaned phone: %1', PhoneNo); // 5551234567
end;
Trim
text.Trim(): Text
Returns a new text with leading and trailing whitespace removed.
Returns: Text - The trimmed text.
var
Input: Text;
begin
Input := ' Hello World ';
Message('[%1]', Input.Trim()); // [Hello World]
end;
TrimStart
text.TrimStart([chars: Text]): Text
Returns a new text with leading whitespace removed. When chars is given, every leading character that appears in chars is removed instead.
Returns: Text - The text with leading characters removed.
var
Input: Text;
begin
Input := ' Hello World ';
Message('[%1]', Input.TrimStart()); // [Hello World ]
Input := '000123';
Message('%1', Input.TrimStart('0')); // 123
end;
TrimEnd
text.TrimEnd([chars: Text]): Text
Returns a new text with trailing whitespace removed. When chars is given, every trailing character that appears in chars is removed instead.
Returns: Text - The text with trailing characters removed.
var
Input: Text;
begin
Input := ' Hello World ';
Message('[%1]', Input.TrimEnd()); // [ Hello World]
Input := 'C:\Temp\';
Message('%1', Input.TrimEnd('\')); // C:\Temp
end;
PadLeft
text.PadLeft(totalLength: Integer[, padChar: Text]): Text
Pads the text on the left side to reach the specified total length. By default, spaces are used for padding.
Returns: Text - The padded text.
| Parameter | Type | Description |
|---|---|---|
| totalLength | Integer | The desired total length of the result |
| padChar | Text | Optional. A one-character text to use for padding |
var
Number: Text;
begin
Number := '42';
Message('Padded: %1', Number.PadLeft(6, '0')); // 000042
end;
PadRight
text.PadRight(totalLength: Integer[, padChar: Text]): Text
Pads the text on the right side to reach the specified total length. By default, spaces are used for padding.
Returns: Text - The padded text.
| Parameter | Type | Description |
|---|---|---|
| totalLength | Integer | The desired total length of the result |
| padChar | Text | Optional. A one-character text to use for padding |
var
Caption: Text;
begin
Caption := 'Item';
Message('[%1]', Caption.PadRight(10, '.')); // [Item......]
end;
Remove
text.Remove(startIndex: Integer[, count: Integer]): Text
Returns a new text with characters removed starting at the specified 1-based position. If count is not specified, all characters from startIndex to the end are removed.
Returns: Text - The text with characters removed.
| Parameter | Type | Description |
|---|---|---|
| startIndex | Integer | The 1-based starting position |
| count | Integer | Optional. Number of characters to remove |
var
Input: Text;
begin
Input := 'Hello Beautiful World';
Message(Input.Remove(6, 10)); // Hello World
Message(Input.Remove(6)); // Hello
end;
Substring
text.Substring(startIndex: Integer[, length: Integer]): Text
Extracts a portion of the text starting at the specified 1-based position. If length is not specified, returns everything from startIndex to the end.
Returns: Text - The extracted substring.
| Parameter | Type | Description |
|---|---|---|
| startIndex | Integer | The 1-based starting position |
| length | Integer | Optional. Number of characters to extract |
var
Input: Text;
begin
Input := 'Hello World';
Message(Input.Substring(7)); // World
Message(Input.Substring(1, 5)); // Hello
end;
Split
text.Split(separator1: Text[, separator2: Text, ...]): List of [Text]
Splits the text into a list of substrings on every occurrence of any of the separators. The result is a 1-based List of [Text] that you can iterate with foreach.
Returns: List of [Text] - The list of substrings.
| Parameter | Type | Description |
|---|---|---|
| separator1.. | Text | One or more delimiters to split on |
var
CsvLine: Text;
Parts: List of [Text];
Part: Text;
begin
CsvLine := 'John,Smith,john@example.com,New York';
Parts := CsvLine.Split(',');
foreach Part in Parts do
Message('Value: %1', Part);
Message('Second value: %1', Parts.Get(2)); // Smith
end;
Building Longer Text
There is no TextBuilder type in ToolBox. Build text with the + operator or +=, which the interpreter handles efficiently for script-sized output. To insert a line break, put the character code 10 into a Text variable using string indexing:
var
NL: Text;
Report: Text;
begin
NL[1] := 10; // a one-character text holding a line feed
Report += 'Customer Report' + NL;
Report += '===============' + NL;
Message(Report);
end;
For collecting many pieces, a List of [Text] is a convenient buffer: add lines as you go and join them at the end.
Practical Examples
Cleaning Up Data
When importing or processing data, you often need to clean up text values by removing unwanted characters and normalizing whitespace.
var
RawInput: Text;
Cleaned: Text;
begin
RawInput := ' John Smith ';
Cleaned := RawInput.Trim();
Message('Cleaned: [%1]', Cleaned); // [John Smith]
// Remove extra characters from a phone number
RawInput := '+1 (555) 123-4567';
Cleaned := RawInput.Replace('+', '');
Cleaned := Cleaned.Replace('(', '');
Cleaned := Cleaned.Replace(')', '');
Cleaned := Cleaned.Replace(' ', '');
Cleaned := Cleaned.Replace('-', '');
Message('Phone digits: %1', Cleaned); // 15551234567
end;
Parsing Text
You can use a combination of text methods to parse structured text values. Remember that positions are 1-based.
var
FullName: Text;
SpacePos: Integer;
FirstName: Text;
LastName: Text;
begin
FullName := 'Jane Doe';
SpacePos := FullName.IndexOf(' ');
if SpacePos > 0 then begin
FirstName := FullName.Substring(1, SpacePos - 1);
LastName := FullName.Substring(SpacePos + 1);
Message('First: %1, Last: %2', FirstName, LastName);
end;
end;
Parsing CSV Data
var
CsvData: Text;
Fields: List of [Text];
begin
CsvData := 'Name,City,Amount';
Fields := CsvData.Split(',');
Message('Field 1: %1', Fields.Get(1));
Message('Field 2: %1', Fields.Get(2));
Message('Field 3: %1', Fields.Get(3));
end;
Building Output Text
When you need to build a report or export data as text, accumulate into a Text variable.
var
Customer: Record Customer;
Output: Text;
NL: Text;
LineCount: Integer;
begin
NL[1] := 10;
Output += 'Customer Report' + NL;
Output += '================' + NL + NL;
Customer.SetRange("Country/Region Code", 'US');
if Customer.FindSet() then
repeat
LineCount += 1;
Output += StrSubstNo('%1. %2 - %3 (%4)',
LineCount,
Customer."No.",
Customer.Name,
Customer.City) + NL;
until Customer.Next() = 0;
Output += NL + StrSubstNo('Total: %1 customers', LineCount);
Message(Output);
end;
Case-Insensitive Comparisons
Text comparisons in AL are case-sensitive by default. You can use ToLower() or ToUpper() to perform case-insensitive comparisons.
var
UserInput: Text;
Expected: Text;
begin
UserInput := 'Yes';
Expected := 'yes';
// Case-sensitive comparison fails
if UserInput = Expected then
Message('Match (case-sensitive)')
else
Message('No match (case-sensitive)');
// Case-insensitive comparison succeeds
if UserInput.ToLower() = Expected.ToLower() then
Message('Match (case-insensitive)');
end;
Extracting File Extension
procedure GetFileExtension(FileName: Text): Text
var
DotPos: Integer;
begin
DotPos := FileName.LastIndexOf('.');
if DotPos > 0 then
exit(FileName.Substring(DotPos + 1).ToLower());
exit('');
end;
begin
Message('Extension: %1', GetFileExtension('Report.XLSX')); // xlsx
Message('Extension: %1', GetFileExtension('data.csv')); // csv
Message('Extension: %1', GetFileExtension('noextension')); // (empty)
end;
Validating and Formatting Input
var
Email: Text;
AtPos: Integer;
DotPos: Integer;
Domain: Text;
begin
Email := 'USER@Example.COM';
// Normalize to lowercase
Email := Email.Trim().ToLower();
// Basic validation
AtPos := Email.IndexOf('@');
if AtPos <= 1 then
Error('Invalid email: missing @ symbol');
Domain := Email.Substring(AtPos + 1);
DotPos := Domain.IndexOf('.');
if DotPos <= 1 then
Error('Invalid email: domain must contain a dot');
Message('Validated email: %1', Email);
Message('Domain: %1', Domain);
end;
Building Delimited Strings
procedure BuildCsvLine(Values: List of [Text]): Text
var
Line: Text;
Value: Text;
IsFirst: Boolean;
begin
IsFirst := true;
foreach Value in Values do begin
if not IsFirst then
Line += ',';
// Wrap in quotes if the value contains a comma
if Value.Contains(',') then
Line += '"' + Value + '"'
else
Line += Value;
IsFirst := false;
end;
exit(Line);
end;
Working with Code and Field Values
Because text methods need a Text receiver, copy Code values and record fields into a Text variable before calling methods on them.
var
Item: Record Item;
Description: Text;
begin
if Item.FindSet() then
repeat
Description := Item.Description;
if Description.ToLower().Contains('bike') then
Message('%1: %2', Item."No.", Item.Description);
until Item.Next() = 0;
end;
Summary
Text methods in ToolBox give you a rich set of tools for searching, modifying, and analyzing string data. They follow the native AL rules: the receiver is a Text variable, and all positions are 1-based. Combined with the built-in string functions documented in the Built-in Functions page, you have everything you need to handle text processing tasks in your scripts.