ELI5 FactBoxes in Business Central, and how to make them useful!

We continue the ELI5 series, this time looking at factboxes, those small boxes on the right side of Business Central pages. Join me in this video to learn how to add factboxes to your app.

https://youtu.be/iRvwQ7khrZM


In this video, Erik walks through the fundamentals of FactBoxes in Business Central — what they are, how to build them, and how to make them genuinely useful. He starts from scratch, building a simple FactBox step by step, and then explores captions, navigation, actions, scope, and performance considerations. The accompanying source code takes things further with a reusable “Sales Team” FactBox pattern that can be attached to multiple pages.

What Is a FactBox?

A FactBox is the panel that appears on the right side of a page in Business Central. You can toggle it with the i button — if that button is present on a page, it means the page has FactBoxes configured.

There are two groups of FactBoxes:

  • Details — these are the “true” FactBoxes that you as a developer have control over
  • Attachments, Links, and Notes — these are system FactBoxes where you have limited control

The top position in the Details tab is prime real estate. In modern environments, the AI summary typically claims that spot. Below it, you’ll find things like sales history, statistics, dimensions, and customer details — depending on the page you’re on.

FactBoxes Are Pages Inside Pages

One of the key concepts to understand is that a FactBox is actually a separate page embedded within the host page. The Business Central UI is composed of nested pages. You can verify this yourself by opening the page inspection pane (Ctrl+Alt+F1) and clicking on different areas — you’ll see that each FactBox section is its own distinct page with its own page number and type.

This means that before you can add a FactBox to a page, you need to create a separate page of type ListPart or CardPart:

  • CardPart — displays a single record’s fields (like the customer details FactBox)
  • ListPart — displays a repeater/list of records (like a list of sales orders)
  • HeadlinePart — a different thing entirely, used on Role Centers for headlines like “How many orders this month”

Building a Simple FactBox Step by Step

Erik demonstrates building a FactBox that shows open sales orders on the Customer Card. The process involves two files: the FactBox page itself and a page extension to embed it.

Step 1: Create the FactBox Page

Since we want to show a list of sales orders, we create a ListPart page sourced from the Sales Header table. The SourceTableView property filters it down to only orders (excluding quotes, credit memos, invoices, etc.):

page 50100 "Our FactBox"
{
    Caption = 'Sales Orders!';
    PageType = ListPart;
    SourceTable = "Sales Header";
    SourceTableView = where("Document Type" = const(Order));
    Editable = false;

    layout
    {
        area(Content)
        {
            repeater(Rep)
            {
                field("No."; Rec."No.")
                {
                    ApplicationArea = all;
                    trigger OnDrillDown()
                    begin
                        Page.Run(Page::"Sales Order", Rec);
                    end;
                }
                field("Order Date"; Rec."Order Date")
                {
                    ApplicationArea = all;
                }
                field(Amount; Rec.Amount)
                {
                    ApplicationArea = all;
                }
            }
        }
    }
}

A few important properties to note:

  • Editable = false — FactBoxes are typically read-only. Without this, users would see New, Delete, and Open in Excel options in the menu, which usually isn’t desirable.
  • Caption — sets the label shown at the top of the FactBox.

Step 2: Embed the FactBox in the Customer Card

On the host page, you use a page extension and add the FactBox into the factboxes area using the part control:

pageextension 50100 "Our Customer Card Ext" extends "Customer Card"
{
    layout
    {
        addfirst(factboxes)
        {
            part("Sales Orders"; "Our FactBox")
            {
                ApplicationArea = all;
                SubPageLink = "Sell-to Customer No." = field("No.");
            }
        }
    }
}

The SubPageLink Is Critical

Without the SubPageLink, the FactBox will show all sales orders in the system — not just those for the current customer. The SubPageLink creates the connection between the host page and the FactBox by filtering the FactBox’s source table. In this case, it filters the "Sell-to Customer No." field on the Sales Header to match the "No." field on the Customer table.

Important Details and Gotchas

Captions: Two Places to Set Them

You can set a caption on the FactBox page itself (in the page properties) and also on the part control in the host page extension. If both are set, the host page wins. Think of it this way: the host page is the hosting entity, and it gets to override. Erik recommends putting the caption on the FactBox page itself, because if you reuse the FactBox on multiple pages, you don’t have to repeat the caption everywhere.

The addfirst Position Problem

addfirst(factboxes) is the most popular position for apps to add their FactBoxes. If you have multiple apps adding FactBoxes to the same page at the first position, the order becomes essentially random — the AL engine decides the ordering and it could change. Keep this in mind when building extensions.

Don’t Forget ApplicationArea

If you forget to set the ApplicationArea on your part or fields, nothing will show up. A convenient approach is to set ApplicationArea = all at the page level so you don’t have to remember it on every individual field and action.

Adding Navigation with OnDrillDown

To let users click on a record and navigate to it, add an OnDrillDown trigger on the field. You can use either Page.Run (opens the page independently) or Page.RunModal (opens the page modally, blocking the host card). Both will update the FactBox data when you return, though you can add a CurrPage.Update() call to force a refresh if needed.

Actions and Scope

You can add actions to FactBoxes, but they behave slightly differently. The only action area available on a part is Processing. By default, an action’s scope is Page, meaning it appears in the page’s action bar. If you set Scope = Repeater, the action appears both in the action bar and in the FactBox’s context menu. Unfortunately, there’s no “repeater only” option currently.

Advanced Example: A Reusable Sales Team FactBox

The source code takes the concept further with a practical, real-world pattern: a reusable “Sales Team” FactBox that can be attached to any page — Customer Card, Vendor Card, Posted Sales Invoice, and more. This is done using RecordRef to make the FactBox parent-agnostic.

The Sales Team Member Table

First, a custom table stores the team member assignments with a generic parent reference:

table 50100 "Sales Team Member"
{
    fields
    {
        field(1; ParentTable; Integer)
        {
            Caption = 'Parent Table';
        }
        field(2; ParentSystemId; Guid)
        {
            Caption = 'Parent Record';
        }
        field(3; SalesPerson; Code[20])
        {
            Caption = 'Person';
            TableRelation = "Salesperson/Purchaser".Code;
        }
        field(4; Name; Text[100])
        {
            Caption = 'Name';
            FieldClass = FlowField;
            CalcFormula = Lookup("Salesperson/Purchaser".Name where(Code = field(SalesPerson)));
            Editable = false;
        }
    }
    keys
    {
        Key(PK; ParentTable, ParentSystemId, SalesPerson)
        { }
    }
}

By using ParentTable (the table number) and ParentSystemId (the record’s SystemId), this table can relate to any record in any table — customers, vendors, posted invoices, or anything else.

The Reusable FactBox Page

The FactBox page exposes a SetParent procedure that accepts a RecordRef, making it completely generic:

page 50100 "Sales Team FactBox"
{
    Caption = 'Team';
    PageType = ListPart;
    SourceTable = "Sales Team Member";
    Editable = true;
    InsertAllowed = false;
    ModifyAllowed = false;
    DeleteAllowed = true;
    layout
    {
        area(Content)
        {
            repeater(Rep)
            {
                field(Name; Rec.Name)
                {
                    ApplicationArea = all;
                }
            }
        }
    }
    actions
    {
        area(Processing)
        {
            action(Add)
            {
                Caption = 'Add new';
                ApplicationArea = all;
                Image = Add;
                trigger OnAction()
                var
                    SP: Record "Salesperson/Purchaser";
                begin
                    if Page.RunModal(PAGE::"Salespersons/Purchasers", SP) = Action::LookupOK then begin
                        Rec.Init();
                        Rec.ParentTable := PTable;
                        Rec.ParentSystemId := PSystemId;
                        Rec.SalesPerson := SP.Code;
                        Rec.Insert();
                    end;
                end;
            }
        }
    }
    procedure SetParent(Ref: RecordRef)
    var
        SystemIdField: FieldRef;
    begin
        PTable := Ref.Number;
        SystemIdField := Ref.Field(Ref.SystemIdNo);
        PSystemId := SystemIdField.Value;
        Rec.SetRange(ParentTable, PTable);
        Rec.Setrange(ParentSystemId, PSystemId);
        CurrPage.Update(False);
    end;

    var
        PTable: Integer;
        PSystemId: Guid;
}

Notice how the FactBox uses a lookup action (Page.RunModal on the Salespersons/Purchasers page) to let users add team members. The InsertAllowed = false and ModifyAllowed = false properties prevent direct editing in the repeater, while DeleteAllowed = true lets users remove members.

Embedding on Multiple Pages

The same FactBox is attached to the Customer Card, Vendor Card, and Posted Sales Invoice using an identical pattern. Here’s the Customer Card example:

pageextension 50100 "Team - Customer" extends "Customer Card"
{
    layout
    {
        addfirst(factboxes)
        {
            part(Team; "Sales Team FactBox")
            {
                ApplicationArea = all;
            }
        }
    }
    trigger OnAfterGetCurrRecord()
    var
        Ref: RecordRef;
    begin
        Ref.GetTable(Rec);
        CurrPage.Team.Page.SetParent(Ref);
    end;
}

Instead of using SubPageLink (which is commented out in the source), this approach uses OnAfterGetCurrRecord to dynamically call the SetParent procedure via CurrPage.Team.Page.SetParent(Ref). The RecordRef approach means the FactBox page never needs to know what table it’s being called from — it just extracts the table number and SystemId from whatever record reference it receives.

The Vendor Card and Posted Sales Invoice extensions follow the exact same pattern — only the page extension target changes:

pageextension 50101 "Team - Vendor" extends "Vendor Card"
{
    // Identical pattern - same FactBox, different host page
    ...
}

pageextension 50102 "Team - sales inv" extends "Posted Sales Invoice"
{
    // Identical pattern - same FactBox, different host page
    ...
}

Performance: Page Background Tasks

Erik briefly touches on an advanced topic: FactBoxes get calculated every time you navigate to a record, so they can slow down the page. For simple database lookups with good key coverage, this is usually fine. But if your FactBox needs to call external services (like SharePoint) or run expensive calculations, you should look into Page Background Tasks. These let you run the computation asynchronously and then populate the FactBox with the results when they’re ready, keeping the page snappy.

Summary

FactBoxes are a powerful way to augment Business Central pages with contextual information that users would otherwise have to navigate away to find. Here are the key takeaways:

  • A FactBox is a separate page (of type ListPart or CardPart) embedded into a host page
  • Use SubPageLink or OnAfterGetCurrRecord with a procedure call to connect the FactBox to the host record
  • Set Editable = false to prevent unwanted data manipulation
  • Captions set on the host page’s part control override captions set on the FactBox page
  • Use OnDrillDown triggers to add navigation from FactBox fields to related pages
  • Actions can be scoped to Repeater to appear in the FactBox’s context menu
  • The RecordRef pattern shown in the source code lets you build a single FactBox that works across multiple unrelated pages
  • For expensive operations, consider Page Background Tasks to keep the UI responsive