Stop creating weird Batch Jobs

Here is another video in my series about beginning AL development. This time about Batch Jobs.

https://youtu.be/bTPWakN0Ue0


In this video, Erik explains why you should stop creating custom pages and codeunits for batch processing in Business Central, and instead leverage the built-in Processing Only report pattern. This approach gives you free scheduling, filtering, and parameter saving — all out of the box.

A Brief History: What Is a Batch Job?

Erik takes us on a quick trip down memory lane to Navision 3.56 — the grandfather of Business Central from about 25 years ago. In that version, there was actually a dedicated object type called a Batch Job. These were used for things like batch posting invoices, copying documents, creating accounting years, and deleting orders.

That dedicated object type no longer exists. At some point, the creators realized that a batch job is essentially a report with no output. And that’s exactly how we build them today.

Defining a Batch Job

Erik offers this clear definition:

  • A batch job is a singular function that you can call to do something — post a journal, delete sales orders, run cost adjustment, etc.
  • It typically has some input parameters.
  • It doesn’t produce printed output — the “output” is the result written back into the data.

The Processing Only Report Pattern

The key to building a proper batch job in AL is the ProcessingOnly property on a report object. When set to true, the report has no layout, no print/preview buttons — just an OK button and a request page with filters and parameters.

report 50100 "My Batch"
{
    ProcessingOnly = true;
    UsageCategory = ReportsAndAnalysis;
    ApplicationArea = all;
    dataset
    {
        dataitem(Customer; Customer)
        {
            RequestFilterFields = "Customer Posting Group", "Customer Price Group";
            trigger OnPreDataItem()
            var
                Fancy: Codeunit FancyCo;
            begin
                Fancy.FancyFunc(Customer);
            end;
        }
    }
    requestpage
    {
        layout
        {
            area(content)
            {
                field(Test; Test)
                {
                    ApplicationArea = all;
                    Caption = 'Test thingie';
                }
            }
        }
    }
    var
        Test: Text;
}

codeunit 50100 FancyCo
{
    procedure FancyFunc(var Cust: Record Customer)
    begin
        // Fancy processing
    end;
}

Notice a few things about this approach:

  • ProcessingOnly = true; — This is the “secret sauce.” No layout, no output. It behaves as a batch job.
  • RequestFilterFields — The report automatically generates a filter UI for the user based on the data item. You get all the standard filtering capabilities for free.
  • The requestpage section — You can add custom fields (like the Test text field) to collect additional parameters from the user. You can also add lookups and other page functionality here.

Why Not Just Use a Page and a Codeunit?

Erik says this is the pattern he sees too often: developers create their own page, add an action button, wire it up to a codeunit, and call it a batch job. He believes this is the wrong approach in most cases, and here’s why:

When your process lives inside a Processing Only report, you get all of these capabilities for free:

  1. Scheduling — Users can schedule the report to run every Monday at 10 AM, every night, or whenever they want. Parameters and filters are saved automatically with the scheduled job.
  2. Built-in filtering — The request page gives users full filtering capabilities on the data item without any custom code.
  3. Discoverability — Because it’s a report with UsageCategory, it appears in the search function and can be called independently of any action sitting on a specific page.

The common objection Erik hears is: “But we need to do a lot of stuff on the page to prepare.” That’s not a valid argument — the request page of a report is a page. You can add fields, lookups, and whatever logic you need.

Wiring It Up: The Page Extension

To make the batch job accessible from a relevant list page, you simply add an action that runs the report:

pageextension 50100 CustomerListExt extends "Customer List"
{
    actions
    {
        addfirst(processing)
        {
            action(MyBatch)
            {
                Caption = 'My Batch';
                ApplicationArea = all;
                RunObject = Report "my batch";
            }
        }
    }
}

When the user clicks “My Batch” on the Customer List, they see the request page with the customer filters and any custom parameters — not a print/preview dialog, just an OK button to execute the job.

Processing Data: The Triggers

The data item in the report gives you built-in looping over records. You have three key triggers to work with:

  • OnAfterGetRecord — Fires once per record (e.g., per customer). Use this when you need to process each record individually. The loop is handled for you — no need to write REPEAT...UNTIL. For example, you could append text to each customer’s name and call Modify.
  • OnPreDataItem — Fires before any records are processed. Use this when you need to do something once before the loop starts, or when you want to pass the entire filtered record set to a codeunit for processing.
  • OnPostDataItem — Fires after all records have been processed. Use this for cleanup or summary operations.

Erik demonstrates the OnPreDataItem approach, passing the Customer record to a dedicated codeunit for processing. This keeps your business logic cleanly separated while still taking advantage of the report’s filtering and scheduling infrastructure:

trigger OnPreDataItem()
var
    Fancy: Codeunit FancyCo;
begin
    Fancy.FancyFunc(Customer);
end;

The batch job acts as a thin slice between the user interface (filtering, parameters, scheduling) and your business logic (sitting in the codeunit). It’s the best of both worlds.

A Note on Microsoft’s Base App

Erik also points out that even Microsoft doesn’t always follow this pattern in the base application. He believes Microsoft is missing a big opportunity to “re-batchify” many routines — like posting processes — so they could take advantage of the built-in scheduling and filtering capabilities that Processing Only reports provide.

Conclusion

The next time you need to build a batch process in Business Central, resist the urge to create a standalone page with an action calling a codeunit. Instead, use a Processing Only report. You’ll get scheduling, filtering, parameter persistence, and search discoverability — all for free. Put your business logic in a codeunit, wire it up through the report’s data item triggers, and let the platform do the heavy lifting. Stop creating weird batch jobs.