Script Action
Script actions allow you to write AL scripts directly within APD, without creating separate codeunit objects. This provides a quick way to implement custom logic.
Overview
A Script action:
- Executes AL script code directly
- No separate codeunit required
- Compiled and stored within APD
- Full access to AL language features
When to Use Script Actions
Use Script actions for:
- Quick prototyping
- Simple data operations
- Testing logic before creating codeunits
- One-off custom operations
- Learning and experimentation
Creating a Script Action
Step-by-Step
- Open the Advanced Portal Designer menu page (search for it in Tell Me) and click Actions
- Click New
- Enter the Web Action code
- Set Type to "Script"
- Click the "Edit Script" link
- Write your AL script
- Save and compile
The Script Editor
Opening the Editor
- In the action card, find the Script section
- Click the "Edit Script" link
- The script editor opens
Editor Features
| Feature | Description |
|---|---|
| AL syntax highlighting | Color-coded AL keywords |
| Intellisense | Auto-complete for AL commands |
| Error checking | Compile-time validation |
| Line numbers | Easy reference |
Script Structure
Basic Script
// Variables go at the top
var
Customer: Record Customer;
OutputText: Text;
// Main code block
begin
Customer.FindFirst();
OutputText := Customer.Name;
output(OutputText);
end;
Key Elements
- Variable declarations (optional): Declare any records, text, etc.
- Begin/End block: Contains your main logic
- Output function: Send content to the browser
Script Functions Reference
Script actions have access to the following additional AL commands beyond standard AL:
| Function | Description |
|---|---|
output(<text>) |
Send text/HTML content to the response |
response(<html/redirect>,<ContentType>,<RedirectLink>) |
Set the response type - use to return specific content types or redirect the user |
parm(<parameter>) |
Get a parameter value from the query string or POST data |
action(<name>,[Record Variable]) |
Navigate to another action, optionally passing a record variable |
currentuserid() |
Get the current APD user ID |
setlanguage(<language no>) |
Set the language for the response |
updatesession(<Type>,<Number>,'',<Security Profiles Filter>) |
Update the current session. Type: 1=Customer, 2=Vendor, 3=Contact, 4=Salesperson |
stripestatus(<Stripe Session>,<SettingToGet>) |
Get the status of a Stripe payment session |
output()
Use output() to send content to the response:
begin
output('Hello, World!');
output('<h1>Welcome</h1>');
output('<p>Current time: ' + format(CurrentDateTime) + '</p>');
end;
response()
Use response() to control the response type. This can set the content type for JSON/XML responses, or redirect the user to another page:
begin
// Return a JSON response
response('html', 'application/json', '');
output('{"status": "ok"}');
end;
begin
// Redirect the user to another action
response('redirect', '', 'https://example.com/thankyou');
end;
parm()
Use parm() to retrieve query string or POST parameters:
var
CustomerNo: Code[20];
begin
CustomerNo := parm('custno');
output('Customer: ' + CustomerNo);
end;
action()
Use action() to navigate to another APD action, optionally passing a record:
var
Customer: Record Customer;
begin
Customer.Get('10000');
action('CUSTOMER-DETAIL', Customer);
end;
currentuserid()
Returns the current APD user ID:
begin
output('Current user: ' + currentuserid());
end;
updatesession()
Updates the current user session. Use this for login flows or changing the user context:
begin
// Log in as customer 10000
updatesession(1, '10000', '', '');
end;
Type values:
- 1 = Customer
- 2 = Vendor
- 3 = Contact
- 4 = Salesperson
stripestatus()
Get the status of a Stripe payment session:
var
Status: Text;
begin
Status := stripestatus(parm('session_id'), 'status');
output('Payment status: ' + Status);
end;
Building HTML
var
Html: TextBuilder;
begin
Html.AppendLine('<html>');
Html.AppendLine('<body>');
Html.AppendLine('<h1>Customer List</h1>');
Html.AppendLine('</body>');
Html.AppendLine('</html>');
output(Html.ToText());
end;
Accessing Data
Reading Records
var
Customer: Record Customer;
begin
if Customer.FindSet() then
repeat
output('<p>' + Customer.Name + '</p>');
until Customer.Next() = 0;
end;
Filtering Records
var
SalesHeader: Record "Sales Header";
begin
SalesHeader.SetRange("Document Type", SalesHeader."Document Type"::Order);
SalesHeader.SetRange("Sell-to Customer No.", '10000');
if SalesHeader.FindSet() then
repeat
output('<p>Order: ' + SalesHeader."No." + '</p>');
until SalesHeader.Next() = 0;
end;
Accessing Request Parameters
Using Session
The session record is available as Rec:
begin
if Rec."User Authenticated" then
output('Welcome, ' + Rec.UserID)
else
output('Please log in');
end;
Getting URL Parameters
Use parm() to access query string or POST parameters:
var
CustomerNo: Code[20];
begin
// Access ?custno=10000 parameter
CustomerNo := parm('custno');
output('Customer: ' + CustomerNo);
end;
Generating JSON
For API-style responses:
var
Customer: Record Customer;
JsonArray: JsonArray;
JsonObj: JsonObject;
begin
if Customer.FindSet() then
repeat
Clear(JsonObj);
JsonObj.Add('no', Customer."No.");
JsonObj.Add('name', Customer.Name);
JsonArray.Add(JsonObj);
until Customer.Next() = 0;
output(format(JsonArray));
end;
Error Handling
Using Error
begin
if not ValidateInput() then
Error('Invalid input provided');
// Continue with valid input
end;
Try Functions
[TryFunction]
procedure TryProcessData()
begin
// Code that might fail
end;
begin
if not TryProcessData() then
output('Error: ' + GetLastErrorText());
end;
Script Examples
Example 1: Customer Dashboard
var
Customer: Record Customer;
SalesHeader: Record "Sales Header";
OpenOrders: Integer;
begin
Customer.Get(Rec."Navision number");
SalesHeader.SetRange("Document Type", SalesHeader."Document Type"::Order);
SalesHeader.SetRange("Sell-to Customer No.", Customer."No.");
OpenOrders := SalesHeader.Count;
output('<html><body>');
output('<h1>Welcome, ' + Customer.Name + '</h1>');
output('<p>You have ' + format(OpenOrders) + ' open orders.</p>');
output('</body></html>');
end;
Example 2: JSON API Response
var
Item: Record Item;
JsonArray: JsonArray;
JsonItem: JsonObject;
begin
Item.SetRange(Blocked, false);
if Item.FindSet() then
repeat
Clear(JsonItem);
JsonItem.Add('itemNo', Item."No.");
JsonItem.Add('description', Item.Description);
JsonItem.Add('unitPrice', Item."Unit Price");
JsonItem.Add('inventory', Item.Inventory);
JsonArray.Add(JsonItem);
until Item.Next() = 0;
output(format(JsonArray));
end;
Example 3: Form Handler
var
Customer: Record Customer;
Name: Text;
Email: Text;
begin
Name := parm('name');
Email := parm('email');
if (Name = '') or (Email = '') then begin
output('{"error": "Name and email are required"}');
exit;
end;
Customer.Init();
Customer.Name := Name;
Customer."E-Mail" := Email;
Customer.Insert(true);
output('{"success": true, "customerNo": "' + Customer."No." + '"}');
end;
Compilation
How Compilation Works
- Script is parsed as AL code
- Syntax is validated
- Table/field references are checked
- Compiled form is stored
Compilation Errors
| Error Type | Example | Solution |
|---|---|---|
| Syntax error | Missing semicolon | Fix AL syntax |
| Unknown identifier | Misspelled variable | Check variable names |
| Type mismatch | Text assigned to Integer | Use correct types |
Viewing Errors
Compilation errors appear in the editor with:
- Line number
- Error description
- Quick access to problematic code
Limitations
Script actions have some limitations compared to codeunits:
| Feature | Codeunit | Script |
|---|---|---|
| Separate procedures | Yes | Limited |
| Events/Triggers | Yes | No |
| Unit testing | Yes | No |
| Source control | Yes (AL file) | In BC only |
| Debugging | Full debugger | Limited |
Migrating to Codeunit
When your script grows complex:
- Create a new codeunit in VS Code
- Copy your script logic
- Add proper procedure structure
- Add error handling
- Deploy and test
- Change action type to Codeunit
Best Practices
Keep It Simple
- Scripts are best for simple operations
- Complex logic belongs in codeunits
- Consider maintainability
Security
- Always validate input
- Check authentication where needed
- Don't expose sensitive operations
Performance
- Avoid unnecessary loops
- Use appropriate filters
- Consider caching for repeated calls