Install
$ agentstack add skill-louage-frw-agentic-coding-skill-pages ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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
CardPageIdfor drill-down navigation - Limit FlowFields to 3-4 maximum (each triggers a query per row)
- Use
UsageCategory = Listsfor 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 fieldaddlast(GroupName)/addfirst(GroupName), at end/start of groupmodify(FieldName), change properties of existing fieldmoveafter(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)andarea(Navigation) - Promote key actions using
area(Promoted)withactionref(BC 22+) - Set appropriate
Imageidentifiers - Add
RunObject/RunPageLinkfor navigation actions
Step 4: Implement Dynamic UI
Visible/Editableexpressions, control field/group visibility based on stateStyleExpr, conditional formatting (Favorable, Unfavorable, Attention, Subordinate, Strong)Enabled, disable actions based on record state- Update expressions in
OnAfterGetRecordtrigger
Step 5: Build and Test
- Build:
al_build - Publish:
al_publish(incremental) - Verify layout, navigation, and field behavior
- Test dynamic visibility and styling with different record states
References
- Page Types, Microsoft Docs
- Page Extension Object
- Actions Overview
- Promoted Actions (actionref)
- Field Importance
- Styling with StyleExpr
Constraints
- This skill covers page design, layout, extensions, and dynamic UI patterns
- Do NOT modify standard BC pages directly, use
pageextensionobjects only - Do NOT place more than 3-4 FlowFields on List pages (performance impact)
- Do NOT skip
ApplicationAreaon any field or action - API page design →
skill-api.md| Performance (FlowFields, SetLoadFields) →skill-performance.md| Page testing →skill-testing.md
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Louage
- Source: Louage/frw-agentic-coding
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.