Email this post as a PDFNewDevelopment · 6 November 2026 · 11 min read

AL development from scratch, part 6: actions, FactBoxes and page extensions

Adding actions and promoting them, building a FactBox, saving views on a list, and extending Microsoft's Item Card and Item List with page extensions, with the full Pallet Manager code and every property explained.

Contents
  1. Actions: the basics
  2. FactBoxes
  3. Views on list pages
  4. Page extensions: changing Microsoft's pages
  5. Profiles and page customisations
  6. Common mistakes
  7. Where we are

This is part 6 of AL development from scratch. In part 5 we built a list and a card for pallet types. They show data, but they do not do anything yet, and our two new item fields from part 4 are still invisible on the Item Card.

In this part we add actions (the buttons and menu items), promote the important ones, build a FactBox that shows an item's pallet type at a glance, add saved views to the list, and write page extensions that put Pallet Manager onto Microsoft's Item Card and Item List.

Actions: the basics

Actions live in the actions section of a page, grouped into areas. The area decides where the client shows them:

AreaWhat goes therePallet Manager example
area(Processing)Actions that do something to the data: post, release, calculate, copy, blockBlock and Unblock
area(Navigation)Actions that open related informationItems using this pallet type
area(Reporting)Reports the user can run from herePallet Requirements report
area(Creation)Creating a new related recordNew item
area(Promoted)References to actions defined in the other areas, shown prominently in the action barThe three or four actions users need most

Role Centers have their own areas for navigation menus (area(Sections) and area(Embedding)), which we will not need for Pallet Manager.

Here is the full actions section for the pallet type card:

actions
{
    area(Processing)
    {
        action(Block)
        {
            Caption = 'Block';
            ToolTip = 'Block this pallet type so it can no longer be chosen on items.';
            Image = Reject;
            Enabled = not Rec.Blocked;

            trigger OnAction()
            var
                BlockedMsg: Label 'Pallet type %1 is now blocked.', Comment = '%1 = pallet type code';
            begin
                Rec.Validate(Blocked, true);
                Rec.Modify(true);
                Message(BlockedMsg, Rec.Code);
            end;
        }
        action(Unblock)
        {
            Caption = 'Unblock';
            ToolTip = 'Allow this pallet type to be chosen on items again.';
            Image = ReOpen;
            Enabled = Rec.Blocked;

            trigger OnAction()
            begin
                Rec.Validate(Blocked, false);
                Rec.Modify(true);
            end;
        }
    }
    area(Navigation)
    {
        action(Items)
        {
            Caption = 'Items';
            ToolTip = 'View the items that use this pallet type as their default.';
            Image = Item;
            RunObject = page "Item List";
            RunPageLink = "BBX Default Pallet Type" = field(Code);
        }
    }
    area(Reporting)
    {
        action(PalletRequirements)
        {
            Caption = 'Pallet Requirements';
            ToolTip = 'Calculate how many pallets of each type your open orders need.';
            Image = Report;
            RunObject = report "BBX Pallet Requirements";
        }
    }
    area(Promoted)
    {
        group(Category_Process)
        {
            Caption = 'Process';

            actionref(Block_Promoted; Block)
            {
            }
            actionref(Unblock_Promoted; Unblock)
            {
            }
        }
        actionref(Items_Promoted; Items)
        {
        }
        group(Category_Report)
        {
            Caption = 'Report';

            actionref(PalletRequirements_Promoted; PalletRequirements)
            {
            }
        }
    }
}

The Items action opens Microsoft's Item List, which lives in the Microsoft.Inventory.Item namespace, so add using Microsoft.Inventory.Item; under the namespace line at the top of the card page file when you add this section. The report is the one we build in part 8. Until it exists, comment that action and its actionref out, or the page will not compile.

Two kinds of action

Actions that run code have an OnAction trigger. Block and Unblock are like this. Notice that the code calls Validate and Modify(true) rather than setting the field directly: that way any rules in the table's OnValidate and OnModify triggers run, exactly as if the user had ticked the box. Keep OnAction short. If it grows beyond a few lines, move the logic into a codeunit (part 7) and call it from the action; that also lets other pages, APIs and job queue entries reuse it.

In part 7 we move this into the BBX Pallet Mgt. codeunit, and the action just calls PalletMgt.ConfirmAndBlockPalletType(Rec).

Actions that open something use RunObject. The Items action opens the Item List, and RunPageLink filters it to items whose "BBX Default Pallet Type" matches the current pallet type's code. No code at all.

Promoting actions

Every action appears in the menus for its area. The ones users need often should also be promoted to the action bar at the top of the page. You do that in area(Promoted), not on the action itself.

An actionref points at an action defined elsewhere on the page. You can put actionrefs straight into area(Promoted), as with Items, or group them. Groups called Category_New, Category_Process and Category_Report map to the standard New, Process and Report tabs that users see on Microsoft's pages, so your page feels the same as theirs. You can add your own groups with your own captions for anything else.

In older code you will see Promoted = true, PromotedCategory and PromotedIsBig on the action itself. That is the previous model. It still compiles on older pages, but new pages should use area(Promoted) and actionrefs.

Action properties reference

PropertyWhat it doesExample / default
CaptionThe text on the button or menu itemCaption = 'Block';
ToolTipExplains what the action does. Required for good practice and checked by the code analysersWrite it as an instruction: "Block this pallet type ..."
ApplicationAreaWhich experience shows the action. Inherited from the page if set thereApplicationArea = All;
ImageThe icon. Use one of the standard image names; IntelliSense lists themImage = Reject;
RunObjectA page, report, codeunit or XMLport to run, instead of an OnAction triggerRunObject = page "Item List";
RunPageLinkFilters the page opened by RunObject using values from the current recordRunPageLink = "BBX Default Pallet Type" = field(Code);
RunPageViewA fixed sort order and filter for the page opened by RunObjectRunPageView = where(Blocked = const(false));
RunPageModeOpens the page in View, Edit or Create modeRunPageMode = Create;
ShortcutKeyA keyboard shortcut for the actionShortcutKey = 'Shift+Ctrl+B'; Avoid clashing with standard shortcuts
ScopeOn list pages: Page (acts on the page) or Repeater (acts on the selected line, and appears in the line menu)Defaults to Page
EnabledWhether the action can be clicked. Can be an expressionEnabled = not Rec.Blocked;
VisibleWhether the action is shown. Can be an expressionPrefer Enabled for actions that are sometimes unavailable, so users know they exist
EllipsisAdds "..." to the caption to show that the action asks for more input before it runsFor actions that open a dialog or request page
InFooterBarShows the action as a button in the footer of a NavigatePage wizardBack, Next and Finish on wizards
AccessByPermissionHides the action from users who lack a given permissionAccessByPermission = tabledata "BBX Pallet Type" = M;

Group related actions in the menus with group inside an area if you have many. For most pages, a flat list in each area is clearer.

FactBoxes

A FactBox is a small page shown in the panel on the right of a card or list. It gives context without the user having to navigate anywhere. We want one that shows an item's default pallet type, its size and maximum load, wherever items are listed.

A FactBox is a page of type CardPart (or ListPart, if it shows several records):

namespace BusinessBoxed.PalletManager;

page 50102 "BBX Pallet Type FactBox"
{
    PageType = CardPart;
    ApplicationArea = All;
    SourceTable = "BBX Pallet Type";
    Caption = 'Pallet Type';
    Editable = false;

    layout
    {
        area(Content)
        {
            field(Code; Rec.Code)
            {
                trigger OnDrillDown()
                begin
                    Page.Run(Page::"BBX Pallet Type Card", Rec);
                end;
            }
            field(Description; Rec.Description)
            {
            }
            field(Material; Rec.Material)
            {
            }
            field("Length (mm)"; Rec."Length (mm)")
            {
            }
            field("Width (mm)"; Rec."Width (mm)")
            {
            }
            field("Max. Weight (kg)"; Rec."Max. Weight (kg)")
            {
            }
        }
    }
}

On its own, this page would show the first pallet type in the table. It becomes useful when another page embeds it with a part control and passes a filter through SubPageLink:

part(BBXPalletType; "BBX Pallet Type FactBox")
{
    ApplicationArea = All;
    SubPageLink = Code = field("BBX Default Pallet Type");
}

That reads: show the FactBox, filtered so its Code equals the "BBX Default Pallet Type" of the record on the hosting page. As the user moves from item to item, the FactBox follows. We put it on the Item List and Item Card shortly.

The OnDrillDown trigger on Code lets the user click the code to open the full card. The FactBox is read-only, which is right: FactBoxes are for looking, not editing.

Alongside your own FactBoxes, add the system ones. systempart(Links; Links) and systempart(Notes; Notes) give users record links and notes for free, and they expect to find them on every card and list. We added both to our list and card in part 5.

A word of caution: every FactBox reads data, and on a list it refreshes each time the user moves to another row. One or two well-chosen FactBoxes help; six slow ones make a page sluggish. Keep FactBoxes simple and avoid heavy calculations in them.

Views on list pages

A view is a saved filter and sort order shown as a tab at the top of a list. Users can create their own, but you can ship useful ones with the page. Add this to "BBX Pallet Type List", after actions:

views
{
    view(Active)
    {
        Caption = 'Active';
        Filters = where(Blocked = const(false));
    }
    view(Blocked)
    {
        Caption = 'Blocked';
        Filters = where(Blocked = const(true));
    }
    view(Wood)
    {
        Caption = 'Wooden pallets';
        Filters = where(Material = const(Wood));
        OrderBy = ascending(Description);
    }
}

The user sees "All" plus your three views, one click each. Views cost nothing in performance and save users from building the same filters again and again.

Page extensions: changing Microsoft's pages

You cannot edit Microsoft's pages any more than their tables. You extend them, with a page extension, which describes changes relative to controls that already exist.

What a page extension can do

KeywordWhat it does
addfirst(anchor)Adds your controls or actions as the first items inside the anchor
addlast(anchor)Adds them as the last items inside the anchor
addafter(anchor)Adds them directly after the anchor control or action
addbefore(anchor)Adds them directly before the anchor
movefirst / movelastMoves existing controls to the start or end of the anchor
moveafter / movebeforeMoves existing controls to just after or before the anchor
modify(control)Changes some properties of an existing control, or adds triggers to it

The anchor is the name of an existing control, group, area or action on the base page. You can anchor on an area name such as content, factboxes, processing or navigation, or on a specific group, field or action.

What you cannot do: remove controls, change a control's source expression, change the page's source table or page type, or replace the base page's triggers. You can hide a control by modifying its Visible property, but think twice before hiding something users expect.

Properties you can change with modify include Visible, Caption, ToolTip and Importance. As with table extensions, the compiler flags any property that cannot be modified, so you find out immediately.

Finding the anchor names

To add something "after the Description field on the Item Card", you need the control name of that field on that page, which is not always the same as the field's caption. Two ways to find it:

  • Go to the definition. In your page extension, put the cursor on the base page's name (for example "Item Card") and press F12. Visual Studio Code opens the page from the downloaded symbols, where every group, field and action is listed with its name. This is the reliable way.
  • Page Inspector. In the Business Central client, press Ctrl+Alt+F1 on the page. It shows the page number and name, the source table, the fields behind each control, and which extensions already change the page. It is the quickest way to find out which page and table you are looking at; then use F12 to find the exact control names.

Anchoring on an area (content, factboxes) is the safest choice when you add a whole new group or FactBox: areas never disappear. Anchoring on a specific field gives a better position, but if Microsoft renames or moves that field in a future release, your extension may need updating. That trade-off is discussed in writing AL extensions that survive release waves.

The BBX Item Card page extension

namespace BusinessBoxed.PalletManager;

using Microsoft.Inventory.Item;

pageextension 50100 "BBX Item Card" extends "Item Card"
{
    layout
    {
        addlast(content)
        {
            group(BBXPallet)
            {
                Caption = 'Pallet';

                field("BBX Default Pallet Type"; Rec."BBX Default Pallet Type")
                {
                    ApplicationArea = All;
                }
                field("BBX Units per Pallet"; Rec."BBX Units per Pallet")
                {
                    ApplicationArea = All;
                }
            }
        }
        addfirst(factboxes)
        {
            part(BBXPalletType; "BBX Pallet Type FactBox")
            {
                ApplicationArea = All;
                SubPageLink = Code = field("BBX Default Pallet Type");
            }
        }
    }

    actions
    {
        addlast(navigation)
        {
            action(BBXPalletTypes)
            {
                ApplicationArea = All;
                Caption = 'Pallet Types';
                ToolTip = 'View and edit the pallet types that items can use.';
                Image = Setup;
                RunObject = page "BBX Pallet Type List";
            }
        }
    }
}

This adds a new FastTab called "Pallet" at the end of the Item Card, with our two fields, puts the pallet FactBox at the top of the FactBox panel, and adds a navigation action to open the pallet type list.

Things to notice:

  • The file starts with our namespace and using Microsoft.Inventory.Item;, because the Item Card page is in Microsoft's Microsoft.Inventory.Item namespace. Our own FactBox and list page need no using: they are in the same namespace as the extension. As always, namespaces are optional (see part 1); without them, drop both lines.
  • The new group, field and action names carry the BBX prefix (BBXPallet, BBXPalletType, BBXPalletTypes). Control names in a page extension share one set of names with the base page and every other extension of it, so they must be unique too.
  • ApplicationArea is set on each control. Set it on every control you add in a page extension, so your controls never depend on how the base page happens to be defined.
  • Captions and tooltips come from the table extension. We only set them on the action, which has no table field behind it.
  • The weight check from part 4 runs here too, because it lives in the table extension. Enter 200 units of a 5 kg item on a pallet rated for 800 kg and the error appears, without a line of code on the page.

The BBX Item List page extension

namespace BusinessBoxed.PalletManager;

using Microsoft.Inventory.Item;

pageextension 50101 "BBX Item List" extends "Item List"
{
    layout
    {
        addfirst(factboxes)
        {
            part(BBXPalletType; "BBX Pallet Type FactBox")
            {
                ApplicationArea = All;
                SubPageLink = Code = field("BBX Default Pallet Type");
            }
        }
    }

    actions
    {
        addlast(navigation)
        {
            action(BBXPalletTypes)
            {
                ApplicationArea = All;
                Caption = 'Pallet Types';
                ToolTip = 'View and edit the pallet types that items can use.';
                Image = Setup;
                RunObject = page "BBX Pallet Type List";
            }
        }
    }
}

Now, scrolling through the Item List, the FactBox shows each item's pallet type as the user moves between rows.

To add our fields as columns on the Item List as well, you add them inside the list's repeater, using addlast with the repeater's name as the anchor. Find that name with F12 on "Item List", as described above, and add the fields with Visible = false so they are available through personalisation without widening everyone's list by default.

Modifying an existing control

The syntax for changing a control that Microsoft owns looks like this:

layout
{
    modify(Description)
    {
        Importance = Promoted;
    }
}

Use modify sparingly. Every change to a standard control is something that users will not find described in Microsoft's documentation, and something to recheck after major releases.

Profiles and page customisations

Page extensions change a page for every user. Sometimes you want a page to look different only for one role, for example a simpler Item Card for warehouse staff. That is what profiles (roles) and page customisations are for. A profile object defines a role and its Role Center, and a pagecustomization object hides, moves or changes controls on a page for users with that profile. Page customisations are limited to layout and actions; they cannot add new fields or code. Most customers start by letting administrators customise roles in the client, which produces the same result without code. We cover them only briefly in this series because a first extension rarely needs them; every object type explained has the overview.

Common mistakes

  • Business logic in OnAction. Keep actions thin and call a codeunit, so the same logic is available everywhere.
  • Setting fields directly in an action (Rec.Blocked := true) instead of using Validate and Modify(true), which skips the table's rules.
  • Using the old Promoted properties on new pages instead of area(Promoted) with actionrefs.
  • Promoting everything. If every action is promoted, none of them stand out. Promote the few that users need daily.
  • Actions without ToolTips. Users rely on them, and the code analysers flag them.
  • Forgetting ApplicationArea on controls added by a page extension. Set it on every control you add.
  • Unprefixed control names in page extensions, which can clash with other apps.
  • Guessing anchor names. Use F12 on the base page; captions and control names often differ.
  • Heavy FactBoxes on lists. They refresh on every row change.
  • Hiding standard fields with modify when the real need is a role-specific layout, which profiles and user personalisation handle better.

For the design side of all this, which actions to promote, how many FactBoxes, which views to ship, see page design in AL: FactBoxes, actions, views and the things users notice.

Where we are

Pallet Manager now looks and feels like part of Business Central. Pallet types have a list with views, a card with actions, and a FactBox, and every item carries a default pallet type on its card with the weight rule enforced. What we have not written yet is any real logic outside the tables. In part 7 we learn the AL language properly: codeunits, variables, working with records, and events.

Previous: part 5: building a page

Next: part 7: codeunits, AL basics, working with records and events

Back to the series contents

Want help with your first extension? Book a 30-minute call.

Email this post as a PDF
A branded PDF of this post, sent to your inbox to keep, print or share. Copy in colleagues if you like.
We only use these addresses to send this PDF. Nobody is added to a mailing list. Mike at Business Boxed receives a copy. See our privacy notice.
Email this post
Talk to us
Want this done in your Business Central?
Book a 30 minute callGet the monthly newsletter

Related posts