5.3: Page Extensions & Page Customizations

Key Takeaways

  • Page extensions (pageextension) additively augment base application or third-party pages using placement keywords (addfirst, addlast, addbefore, addafter) and rearrangement directives (movefirst, movelast, movebefore, moveafter).
  • The modify() keyword in page extensions allows changing properties of existing controls and actions (Visible, Enabled, Editable, Importance, ToolTip), but cannot alter underlying field data types or primary keys.
  • Page customizations (pagecustomization) provide role-specific UI adaptations without code; they are strictly declarative and cannot declare AL variables, global procedures, or page triggers.
  • Profile objects (profile) bind page customizations to specific user roles, configuring default Role Centers and targeted customized layouts without global code modifications.
  • Understanding the page trigger execution lifecycle is vital: OnInit and OnOpenPage run during initialization, OnAfterGetRecord fires for every row rendered in a repeater, OnAfterGetCurrRecord fires for the active cursor, and OnQueryClosePage can evaluate CloseAction to cancel page closure.
Last updated: August 2026

5.3 Page Extensions & Page Customizations

In modern Business Central development, modifying existing user interfaces is achieved without altering base Microsoft code. Developers use Page Extensions (pageextension) to additively introduce new fields, controls, actions, views, and AL triggers to standard pages across the entire tenant. For role-specific UI tailoring without code, developers use Page Customizations (pagecustomization) bound to user Profiles (profile). Understanding the strict architectural differences between these objects, defining custom page views, and mastering the page trigger execution lifecycle is heavily tested on the MB-820 exam.


1. Page Extensions: Additive UI and Logic Enhancement

A pageextension object targets an existing page and applies non-intrusive modifications.

Layout and Action Placement Directives

When adding controls to the layout or actions to the ribbon, AL provides four placement keywords:

  • addfirst(AnchorControl; ...): Inserts the new element as the first child of the specified container.
  • addlast(AnchorControl; ...): Inserts the new element as the last child of the specified container.
  • addbefore(AnchorControl; ...): Inserts the new element immediately preceding the target control or action.
  • addafter(AnchorControl; ...): Inserts the new element immediately following the target control or action.

Modifying Existing Controls: modify()

Existing base application controls and actions can be modified using the modify() block:

  • Permissible modifications: Visible, Enabled, Editable, Importance, ToolTip, Style, StyleExpr.
  • Restrictions: You cannot change the underlying field data type, field length, or primary key bindings.

Moving Existing Controls

Elements can be rearranged without redefining them using move keywords: movefirst(), movelast(), movebefore(), and moveafter().

Adding Predefined Views via views Block

In page extensions targeting List pages, developers can introduce permanent, predefined filter and sort views using the views container:

pageextension 50121 "Customer List Views Ext" extends "Customer List"
{
    views
    {
        addfirst
        {
            view(HighBalanceCustomers)
            {
                Caption = 'High Balance (> 10,000)';
                Filters = where("Balance (LCY)" = filter(> 10000));
                OrderBy = descending("Balance (LCY)");
                SharedLayout = true;
            }
        }
    }
}

Full Page Extension Code Example

pageextension 50120 "Customer Card Loyalty Ext" extends "Customer Card"
{
    layout
    {
        // Add loyalty field immediately after General -> Name
        addafter(Name)
        {
            field("Loyalty Tier Code"; Rec."Loyalty Tier Code")
            {
                ApplicationArea = All;
                ToolTip = 'Specifies the customer loyalty membership tier.';
                Importance = Promoted;
            }
        }
        // Modify standard credit limit field to always be highlighted
        modify("Credit Limit (LCY)")
        {
            Importance = Promoted;
            Style = StrongAccent;
        }
    }

    actions
    {
        addlast(processing)
        {
            action(RecalculateLoyaltyPoints)
            {
                ApplicationArea = All;
                Caption = 'Recalculate Points';
                Image = Refresh;
                ToolTip = 'Recomputes loyalty points based on posted sales history.';

                trigger OnAction()
                var
                    LoyaltyMgmt: Codeunit "Loyalty Management";
                begin
                    LoyaltyMgmt.UpdateCustomerPoints(Rec."No.");
                end;
            }
        }
    }
}
Loading diagram...
Business Central Page Trigger Execution Lifecycle

2. Page Customizations vs. Page Extensions

While a pageextension applies modifications to all users globally across the tenant, a Page Customization (pagecustomization) applies layout alterations exclusively to a specific user Profile (Role Center persona).

Architectural Rules & Constraints of pagecustomization

  1. Declarative Only (No Code): A pagecustomization cannot declare AL variables, custom global procedures, or page triggers (OnOpenPage, OnAfterGetRecord, etc.). It can only contain layout and action structural modifications (modify, move, add).
  2. Only Existing Fields: You can only add fields that already exist on the underlying SourceTable of the target page.
  3. Profile Association: A page customization is inactive until referenced by a profile object via the Customizations property.
pagecustomization "Sales Agent Customer Card" customizes "Customer Card"
{
    layout
    {
        // Hide credit limit from junior sales agents
        modify("Credit Limit (LCY)")
        {
            Visible = false;
        }
        // Move phone number to the very top of the General FastTab
        movefirst(General; "Phone No.")
    }
}

profile "Sales Agent"
{
    Caption = 'Sales Representative';
    RoleCenter = "Order Processor Role Center";
    Customizations = "Sales Agent Customer Card";
    Promoted = true;
    Enabled = true;
}

3. The Runtime Execution Lifecycle of Page Triggers

Understanding when triggers execute is vital for performance optimization and bug prevention. Heavy calculations placed in the wrong trigger can cause severe UI lag across the entire tenant.

TriggerExecution Timing & PurposePerformance & Coding Guidelines
OnInitExecutes once when the page is instantiated in memory before controls are created.Used to initialize local variables or determine dynamic control visibility flags. Rec is not yet loaded.
OnOpenPageExecutes once when the page UI is displayed to the user.Used to set runtime filters (SetRange), apply security filters, or initialize singleton records.
OnAfterGetRecordExecutes every time a record is fetched and rendered on the screen. On a List page with 50 displayed rows, this trigger executes 50 times.Critical Performance Hazard: Never execute heavy database queries, REST API calls, or unindexed calculations here. Used strictly for lightweight formatting (e.g., setting StyleExpr variables).
OnAfterGetCurrRecordExecutes when the active cursor focus shifts to a new record.Used to calculate record-specific sub-totals, refresh contextual variables, or update header summary fields.
OnNewRecord(BelowxRec)Executes when a new blank record is initialized.Used to populate default values for new lines before user input.
OnInsertRecord(BelowxRec)Executes before committing a new record to the database from the page.Can return false to cancel insertion. Bypassed if DelayedInsert = true until focus leaves the row.
OnModifyRecord()Executes before saving a modified record to SQL.Can return false to abort modification.
OnDeleteRecord()Executes before deleting a record from the page.Can return false to abort deletion.
OnQueryClosePage(CloseAction)Executes when the user attempts to close the page. Passes CloseAction (OK, Cancel, LookupOK, LookupCancel).Return false to prevent the user from closing the page (e.g., if mandatory fields are missing).
OnClosePageExecutes when the page successfully closes and unloads from memory.Clean up temporary files or log session telemetry.

Validating Page Dismissal: OnQueryClosePage

Developers frequently need to enforce document completeness before a user closes an editing card. The OnQueryClosePage trigger receives a CloseAction parameter indicating how the user closed the window:

trigger OnQueryClosePage(CloseAction: Action): Boolean
begin
    if CloseAction in [Action::OK, Action::LookupOK] then begin
        if Rec.Status = Rec.Status::"In Progress" then begin
            if Rec."Assigned Inspector" = '' then begin
                Message('You must assign an inspector before completing the audit.');
                exit(false); // Cancels page closure and leaves page open
            end;
        end;
    end;
    exit(true); // Allows page closure
end;
Test Your Knowledge

A developer needs to create a tailored user interface for the 'Order Processor' role. The customization must hide the 'Credit Limit' and 'Blocked' fields from the Customer Card specifically for users assigned to this profile, without affecting other users in the company. No AL logic or triggers are required. What is the most maintainable, upgrade-safe AL architecture for this requirement?

A
B
C
D
Test Your Knowledge

In a List page containing 50 displayed customer records, a developer places an unindexed CalcFields(Balance) call inside the OnAfterGetRecord trigger. What performance impact occurs during page rendering in the Web Client?

A
B
C
D
Test Your Knowledge

A developer wants to prevent users from closing a custom Quality Audit Card page if the 'Audit Status' field is set to 'In Progress' and the user clicks OK. Which page trigger should be implemented, and what value must be returned to cancel page closure?

A
B
C
D