| name | skill-pages |
| description | AL page design patterns for Business Central. Use when creating Card, List, ListPart, Document pages, implementing page extensions, or designing UX layouts. |
Skill: AL Page Design
Purpose
Design and customize Business Central pages: Card, List, Document, Worksheet, and RoleCenter page types, page extensions with addafter/addbefore/modify, dynamic UI (visibility, styling), FastTab layout, promoted actions, and FactBoxes.
When to Load
This skill should be loaded when:
- Creating a new page (any type) for a custom or extended table
- Extending a standard BC page with custom fields, groups, or actions
- Designing a Document page with header-lines structure
- Implementing dynamic visibility, conditional styling, or field importance
- Optimizing page performance (FlowField limits, lazy loading)
Core Patterns
Pattern 1: Card Page
Single-record display with FastTab groups:
page 50100 "Contoso Project Card"
{
PageType = Card;
SourceTable = "Contoso Project";
Caption = 'Project Card';
UsageCategory = None; // accessed from list, not search
layout
{
area(Content)
{
group(General)
{
Caption = 'General';
field("No."; Rec."No.")
{
ApplicationArea = All;
Importance = Promoted; // visible in collapsed FastTab
}
field(Description; Rec.Description)
{
ApplicationArea = All;
Importance = Promoted;
}
field(Status; Rec.Status)
{
ApplicationArea = All;
StyleExpr = StatusStyleExpr;
}
field("Starting Date"; Rec."Starting Date")
{
ApplicationArea = All;
}
}
group(Details)
{
Caption = 'Details';
Visible = DetailsVisible; // dynamic visibility
field("Project Manager"; Rec."Project Manager")
{
ApplicationArea = All;
}
field(Budget; Rec.Budget)
{
ApplicationArea = All;
}
}
}
area(FactBoxes)
{
systempart(Notes; Notes) { ApplicationArea = All; }
systempart(Links; Links) { ApplicationArea = All; }
}
}
actions
{
area(Processing)
{
action(Release)
{
ApplicationArea = All;
Caption = 'Release';
Image = ReleaseDoc;
Promoted = true;
PromotedCategory = Process;
PromotedIsBig = true;
trigger OnAction()
begin
Rec.TestField(Status, Rec.Status::Open);
Rec.Validate(Status, Rec.Status::Released);
Rec.Modify(true);
end;
}
}
area(Navigation)
{
action(Entries)
{
ApplicationArea = All;
Caption = 'Ledger Entries';
Image = LedgerEntries;
RunObject = page "Contoso Project Entries";
RunPageLink = "Project No." = field("No.");
}
}
}
var
StatusStyleExpr: Text;
DetailsVisible: Boolean;
trigger OnAfterGetRecord()
begin
StatusStyleExpr := GetStatusStyle();
DetailsVisible := Rec.Status <> Rec.Status::Open;
end;
local procedure GetStatusStyle(): Text
begin
case Rec.Status of
Rec.Status::Open:
exit('Standard');
Rec.Status::Released:
exit('Favorable');
Rec.Status::Closed:
exit('Subordinate');
end;
end;
}
Pattern 2: List Page
Multi-record display with filters and navigation:
page 50101 "Contoso Project List"
{
PageType = List;
SourceTable = "Contoso Project";
Caption = 'Projects';
UsageCategory = Lists;
ApplicationArea = All;
CardPageId = "Contoso Project Card";
Editable = false;
layout
{
area(Content)
{
repeater(Group)
{
field("No."; Rec."No.") { ApplicationArea = All; }
field(Description; Rec.Description) { ApplicationArea = All; }
field(Status; Rec.Status)
{
ApplicationArea = All;
StyleExpr = StatusStyleExpr;
}
field("Starting Date"; Rec."Starting Date") { ApplicationArea = All; }
// Limit FlowFields on list pages — max 3-4 for performance
field("Total Budget"; Rec."Total Budget") { ApplicationArea = All; }
}
}
}
var
StatusStyleExpr: Text;
trigger OnAfterGetRecord()
begin
StatusStyleExpr := GetStatusStyle();
end;
}
List page rules:
- Set
Editable = false — use Card for editing
- Set
CardPageId for drill-down navigation
- Limit FlowFields to 3-4 maximum (each triggers a query per row)
- Use
UsageCategory = Lists for search/Tell Me discoverability
Pattern 3: Document Page (Header + Lines)
page 50102 "Contoso Sales Document"
{
PageType = Document;
SourceTable = "Contoso Document Header";
Caption = 'Sales Document';
layout
{
area(Content)
{
group(General)
{
Caption = 'General';
field("No."; Rec."No.") { ApplicationArea = All; }
field("Customer No."; Rec."Customer No.")
{
ApplicationArea = All;
Importance = Promoted;
trigger OnValidate()
begin
CurrPage.Update(); // refresh lines after customer change
end;
}
field("Document Date"; Rec."Document Date") { ApplicationArea = All; }
}
part(Lines; "Contoso Document Subpage")
{
ApplicationArea = All;
SubPageLink = "Document No." = field("No.");
UpdatePropagation = Both; // sync header ↔ lines
}
}
}
}
// Lines subpage
page 50103 "Contoso Document Subpage"
{
PageType = ListPart;
SourceTable = "Contoso Document Line";
Caption = 'Lines';
AutoSplitKey = true; // auto-increment Line No.
layout
{
area(Content)
{
repeater(Lines)
{
field(Type; Rec.Type) { ApplicationArea = All; }
field("No."; Rec."No.") { ApplicationArea = All; }
field(Description; Rec.Description) { ApplicationArea = All; }
field(Quantity; Rec.Quantity) { ApplicationArea = All; }
field("Unit Price"; Rec."Unit Price") { ApplicationArea = All; }
field("Line Amount"; Rec."Line Amount")
{
ApplicationArea = All;
Editable = false;
}
}
}
}
}
Pattern 4: Page Extension
Extend standard BC pages without modifying them:
pageextension 50100 "Customer Card Ext" extends "Customer Card"
{
layout
{
addafter(General)
{
group(ContosoInfo)
{
Caption = 'Contoso Information';
field("Contoso Loyalty Points"; Rec."Contoso Loyalty Points")
{
ApplicationArea = All;
ToolTip = 'Total loyalty points for this customer.';
}
}
}
modify(Name)
{
// Change properties of existing fields
StyleExpr = NameStyleExpr;
}
moveafter("Post Code"; City) // reorder fields
}
actions
{
addlast(Processing)
{
action(ContosoCalculatePoints)
{
ApplicationArea = All;
Caption = 'Recalculate Points';
Image = Calculate;
Promoted = true;
PromotedCategory = Process;
trigger OnAction()
var
LoyaltyMgt: Codeunit "Contoso Loyalty Management";
begin
LoyaltyMgt.RecalculatePoints(Rec."No.");
CurrPage.Update(false);
end;
}
}
}
var
NameStyleExpr: Text;
trigger OnAfterGetRecord()
begin
NameStyleExpr := GetNameStyle();
end;
}
Extension placement keywords:
addafter(AnchorField) / addbefore(AnchorField) — position relative to existing field
addlast(GroupName) / addfirst(GroupName) — at end/start of group
modify(FieldName) — change properties of existing field
moveafter(Target; FieldToMove) / movebefore(Target; FieldToMove) — reorder
Pattern 5: Field Importance and Promoted Actions
Control visibility and prominence:
// Field importance — controls FastTab collapsed behavior
field(CriticalField; Rec.CriticalField)
{
ApplicationArea = All;
Importance = Promoted; // always visible (even when FastTab collapsed)
}
field(StandardField; Rec.StandardField)
{
ApplicationArea = All;
Importance = Standard; // visible when FastTab expanded (default)
}
field(RareField; Rec.RareField)
{
ApplicationArea = All;
Importance = Additional; // hidden unless "Show more" clicked
}
// Promoted actions — modern actionref syntax (BC 22+)
actions
{
area(Processing)
{
action(Post) { Caption = 'Post'; Image = Post; }
action(Release) { Caption = 'Release'; Image = ReleaseDoc; }
}
area(Promoted)
{
group(Category_Process)
{
Caption = 'Process';
actionref(Post_Promoted; Post) { }
actionref(Release_Promoted; Release) { }
}
}
}
Workflow
Step 1: Determine Page Type
| Need | PageType | Key Properties |
|---|
| Single record editing | Card | UsageCategory = None, has Card groups |
| Multi-record browsing | List | CardPageId, Editable = false, FlowField limits |
| Header + lines editing | Document | part() with ListPart subpage, UpdatePropagation |
| Batch data entry | Worksheet | Editable repeater, no Card drill-down |
| Dashboard/home page | RoleCenter | part() with cues, activities, headlines |
| Embed in another page | ListPart/CardPart | Used inside part() only |
Step 2: Design Layout
- Group related fields into FastTabs (
group())
- Set
Importance — Promoted for key fields, Additional for rare ones
- Add FactBoxes (
area(FactBoxes)) for contextual info
- Add Notes and Links system parts
- For Document pages, create the ListPart subpage with
AutoSplitKey
Step 3: Add Actions and Navigation
- Define actions in
area(Processing) and area(Navigation)
- Promote key actions using
area(Promoted) with actionref (BC 22+)
- Set appropriate
Image identifiers
- Add
RunObject / RunPageLink for navigation actions
Step 4: Implement Dynamic UI
Visible / Editable expressions — control field/group visibility based on state
StyleExpr — conditional formatting (Favorable, Unfavorable, Attention, Subordinate, Strong)
Enabled — disable actions based on record state
- Update expressions in
OnAfterGetRecord trigger
Step 5: Build and Test
- Build:
Bash: al compile (al workspace compile for a multi-project workspace)
- Publish via VS Code (
AL: Publish without Debugging / RAD) or the AL-Go/CI pipeline — agents generate the code; a human or pipeline deploys
- Verify layout, navigation, and field behavior
- Test dynamic visibility and styling with different record states
References
Constraints
- This skill covers page design, layout, extensions, and dynamic UI patterns
- Do NOT modify standard BC pages directly — use
pageextension objects only
- Do NOT place more than 3-4 FlowFields on List pages (performance impact)
- Do NOT skip
ApplicationArea on any field or action
- API page design →
skill-api.md | Performance (FlowFields, SetLoadFields) → skill-performance.md | Page testing → skill-testing.md