6.4 Processing-Only Reports & Report Substitutions

Key Takeaways

  • Setting ProcessingOnly = true creates a report dedicated exclusively to batch data modifications, calculations, and scheduled tasks, completely bypassing layout compilation, document rendering, and print spooling.
  • The report execution trigger lifecycle follows a deterministic order: OnInitReport -> requestpage triggers -> OnPreReport -> OnPreDataItem -> OnAfterGetRecord (per record) -> OnPostDataItem -> OnPostReport.
  • Flow control functions CurrReport.Skip(), CurrReport.Break(), and CurrReport.Quit() control iteration execution across individual records, dataitems, and the entire report.
  • Interactive batch processing reports utilize progress dialogs (Dialog.Open, Dialog.Update) that must strictly be guarded with if GuiAllowed() then to prevent fatal runtime exceptions in headless Job Queue sessions.
  • The report substitution pattern uses the ReportManagement.OnAfterSubstituteReport event subscriber to redirect standard report invocations to custom report objects without modifying base application code.
Last updated: August 2026

6.4 Processing-Only Reports & Report Substitutions

Not all reports in Business Central generate visual output. Processing-Only reports are specialized AL report objects used for high-performance batch data processing, automated database maintenance, and scheduled transactional routines. Furthermore, when partners need to replace standard Microsoft document reports with custom implementations, Business Central provides an event-driven Report Substitution mechanism that redirects report calls without modifying base code.


1. Processing-Only Reports: Batch Operations

When a report is designed solely to modify records, calculate ledger entries, or perform periodic batch updates, the ProcessingOnly property is set to true.

report 50110 "Batch Adjust Item Costs"
{
    Caption = 'Batch Adjust Item Costs';
    ProcessingOnly = true;
    UseRequestPage = true;
    UsageCategory = Tasks;
    ApplicationArea = Basic, Suite;

    dataset
    {
        dataitem(Item; Item)
        {
            DataItemTableView = sorting("No.") where(Blocked = const(false));
            RequestFilterFields = "No.", "Item Category Code";

            trigger OnPreDataItem()
            begin
                TotalRecords := Item.Count();
                CurrentRecord := 0;
                if GuiAllowed() then
                    ProgressDialog.Open('Adjusting item costs...\Progress: @1@@@@@@@@@@@@@@@@@\Current Item: #2###############');
            end;

            trigger OnAfterGetRecord()
            begin
                CurrentRecord += 1;
                if GuiAllowed() then begin
                    ProgressDialog.Update(1, Round(CurrentRecord / TotalRecords * 10000, 1));
                    ProgressDialog.Update(2, Item."No.");
                end;

                Item.Validate("Unit Cost", Item."Unit Cost" * CostFactor);
                Item.Modify(true);
            end;

            trigger OnPostDataItem()
            begin
                if GuiAllowed() then
                    ProgressDialog.Close();
            end;
        }
    }

    var
        ProgressDialog: Dialog;
        CostFactor: Decimal;
        TotalRecords: Integer;
        CurrentRecord: Integer;
}

Architectural Characteristics of Processing-Only Reports

  • No Layout Requirement: The compiler does not generate or validate RDLC, Word, or Excel layouts. Attempting to define a rendering block or layout file on a processing-only report results in a compilation error.
  • UseRequestPage: When set to true, the user can configure filters and options before processing. When set to false, the report executes immediately upon invocation—ideal for automated background jobs.
  • Transactional Integrity: All record modifications executed inside OnAfterGetRecord participate in the active database transaction. If an unhandled error occurs, the entire batch rolls back unless explicit Commit() calls are made.

2. Progress Dialogs & Headless Safety

In interactive desktop sessions, batch processes should provide visual feedback using the AL Dialog data type.

Dialog String Formatting

  • #1######: Represents a text or value placeholder aligned left.
  • @1@@@@@@: Represents a graphical percentage progress bar where the updated value must be an integer between 0 and 10000 (representing 0.00% to 100.00%).
  • \: Line break character inside dialog strings.

Exam Watchout — The GuiAllowed() Rule: Business Central frequently executes batch reports in background sessions, Job Queues, or API web service calls. Invoking Dialog.Open() or Message() in a non-interactive session throws a fatal runtime exception. Developers must always guard dialog operations with if GuiAllowed() then.

Loading diagram...
Complete Report & DataItem Trigger Execution Lifecycle

3. Report Trigger Lifecycle & Flow Control

Understanding the exact sequence of trigger execution is crucial for initializing variables, applying dynamic filters, and managing state across report dataitems.

Complete Report Execution Sequence

  1. OnInitReport: Runs once when the report is loaded into memory. Used to set default variable values before the request page opens.
  2. requestpage Triggers: OnInit -> OnOpenPage -> (User interactions) -> OnQueryClosePage.
  3. OnPreReport: Runs after the request page closes but before any data is fetched. Used to verify global preconditions, check licensing, or initialize shared data buffers.
  4. OnPreDataItem: Runs once per DataItem before the table cursor is opened. Used to set dynamic filters using AL code (SetRange, SetFilter) or apply sorting keys.
  5. OnAfterGetRecord: Runs for every individual record fetched from the DataItem table. Used to perform calculations, evaluate custom business logic, and manipulate export variables.
  6. OnPostDataItem: Runs once per DataItem after all its records have been iterated.
  7. OnPostReport: Runs once after all DataItems have finished processing. Used for cleanup, logging telemetry signals, or triggering outbound email notifications.

Report Flow Control Statements

AL provides three specialized methods on CurrReport to alter loop execution dynamically:

Flow Control MethodBehavioral Scope
CurrReport.Skip()Skips the current record in the active DataItem. The report engine abandons the rest of OnAfterGetRecord, does not emit a row to the dataset for this record, and proceeds immediately to the next record.
CurrReport.Break()Immediately terminates iteration of the active DataItem. The report skips all remaining records in this DataItem, executes OnPostDataItem, and proceeds to the next sibling or parent DataItem.
CurrReport.Quit()Immediately terminates execution of the entire report. No further DataItems are processed, OnPostReport is bypassed, and control returns to the caller.

4. Report Substitution Architecture

In standard Business Central implementations, end users trigger standard document reports (such as Report 1306 Standard Sales - Invoice) from dozens of posted document pages, list actions, and automated posting routines. Modifying every base page action to point to a custom report is impossible in modern extensions.

To solve this, Business Central provides the Report Substitution Pattern via Codeunit 44 ReportManagement.

+-----------------------------------------------------------------------+
|                      REPORT SUBSTITUTION PATTERN                      |
|                                                                       |
|   User clicks "Print" on Posted Sales Invoice (Calls Report 1306)     |
|                               │                                       |
|                               v                                       |
|   Codeunit 44 "ReportManagement" invokes OnAfterSubstituteReport      |
|                               │                                       |
|                               v                                       |
|   Custom Subscriber intercepts:                                       |
|   if ReportId = Report::"Standard Sales - Invoice" then               |
|       NewReportId := Report::"Contoso Custom Sales Invoice";          |
|                               │                                       |
|                               v                                       |
|   Business Central executes Report 50100 instead of Report 1306       |
+-----------------------------------------------------------------------+

Implementing Report Substitution in AL

Developers subscribe to the OnAfterSubstituteReport event published by Codeunit ReportManagement:

codeunit 50102 "Report Substitution Handler"
{
    [EventSubscriber(ObjectType::Codeunit, Codeunit::ReportManagement, 'OnAfterSubstituteReport', '', false, false)]
    local procedure OnAfterSubstituteReport(ReportId: Integer; var NewReportId: Integer)
    begin
        if ReportId = Report::"Standard Sales - Invoice" then
            NewReportId := Report::"Contoso Custom Invoice";
            
        if ReportId = Report::"Purchase Order" then
            NewReportId := Report::"Contoso Purchase Order";
    end;
}
  • When any codeunit, page action, or user clicks a button calling Report.Run(Report::"Standard Sales - Invoice", ...), the platform raises OnAfterSubstituteReport.
  • The subscriber inspects ReportId. If it matches the target base report, it reassigns NewReportId to the custom report object ID.
  • The platform seamlessly executes the custom report in place of the base report across all standard system entry points.

Report Extensions (reportextension) vs. Report Substitution

Customization GoalRecommended Approach
Adding 2-3 custom fields and a modified RDLC/Excel layout to a standard invoicereportextension Object: Keeps the base report architecture intact while adding columns, dataitems, and layouts additively.
Completely replacing the entire invoice calculation engine, layout hierarchy, or dataset structureReport Substitution Pattern: Completely decouples from the standard report by executing an independent custom report object via Codeunit 44.
Test Your Knowledge

A developer is building a batch maintenance report in AL that recalculates customer risk levels across 100,000 records on a scheduled nightly basis. The report should never generate visual output, print documents, or prompt for layout rendering. Which property combination should be configured in the report definition?

A
B
C
D
Test Your Knowledge

A developer authors a batch processing report that iterates through item ledger entries. To show execution progress during interactive user runs, the report includes a progress dialog. What coding practice must be applied to prevent runtime exceptions when the report is executed automatically by the Job Queue in a background session?

A
B
C
D
Test Your Knowledge

An organization wants all standard system actions that execute the base 'Standard Sales - Invoice' (Report 1306) to run their custom report 'Contoso Sales Invoice' (Report 50100) instead, without modifying any standard page actions or posting codeunits. How is this achieved in modern Business Central development?

A
B
C
D
Test Your Knowledge

During the execution of OnAfterGetRecord in an AL report, a developer wants to immediately exit the active DataItem, skip all remaining records in that table cursor, trigger OnPostDataItem, and continue execution with the next sibling or parent DataItem. Which CurrReport method should be invoked?

A
B
C
D