Why this.proforma() Is Not Reliable in Batch and Why prepareProforma() Exists in D365 Finance and Operations

By Deovandski Skibinski | March 11, 2026

When working with the FormLetter framework in Dynamics 365 Finance & Operations (and AX 2012+), it is a common assumption that this.proforma() can be used to reliably detect whether the current posting run is a proforma. While this assumption generally holds true for interactive (non‑batch) executions, it breaks down in batch and multithreaded scenarios.

The reason is architectural.

Proforma postings are transactional and disposable

A proforma posting intentionally rolls back all database changes after the document is generated. Microsoft has confirmed that proforma runs are effectively “print‑only” operations that leave no persistent system trace unless custom logic explicitly runs outside the posting transaction.
This rollback behavior already makes proforma detection fragile if your logic depends on state after posting.

Batch execution changes the call stack and lifecycle

In batch (especially parallel batch), posting does not execute in the same object lifecycle as interactive runs:
  • SalesFormLetter_* instances may be reused
  • afterOperationBody() may execute outside the scope where the proforma flag is meaningful
  • The boolean behind this.proforma() can be reset, defaulted, or evaluated too late
This is not unique to proforma detection. The Dynamics community has repeatedly documented that event handlers, CoC methods, and flags that work interactively often do not fire or behave the same in batch, due to the service‑based and multithreaded posting pipeline.

Microsoft explicitly introduced prepareProforma() for batch correctness

The FormLetter framework was refactored starting in AX 2012 to support parallel batch execution, which fundamentally changed where customization points must live.
As part of this design, Microsoft introduced FormletterService.prepareProforma()
  • It is invoked only for proforma runs
  • It executes per journal being processed
  • It runs at the correct time in the batch lifecycle, before posting/rollback occurs
  • It is safe for multi‑threaded execution
[ExtensionOf(classStr(SalesFormLetter_Confirm))]
final class SalesFormLetter_Confirm_StoneridgeSoftware_Extension
{
    protected void afterOperationBody()
    {
        // This is a manual/UI run.
        // For batched/multithreaded SO Confirm, see FormletterService_StoneridgeSoftware_Extension
        next afterOperationBody();
        if (this.proforma() && !this.isInBatch())
        {
            Common sourceTable = this.parmSourceTable();

            if (sourceTable && sourceTable.TableId == tableNum(SalesTable))
            {
                SalesTable salesTable = sourceTable as SalesTable;
                // TODO ACTION
            }

        }
    }
}

[ExtensionOf(classStr(FormletterService))]
final class FormletterService_StoneridgeSoftware_Extension
{
    // prepareProforma is called only during batched run, and it is per journal post being processed
    protected void prepareProforma(FormletterJournalPost _formletterJournalPost)
    {
        // Handle SO Confirmations
        if(this.documentStatus == DocumentStatus::Confirmation && this.formletterType == formletterType::Sales)
        {
            ParmId parmIdLoc = this.parmId;
            SalesTable salesTable;
            SalesParmSubTable salesParmSubTable;
            ttsbegin;
            while select forupdate salesTable
                            join salesParmSubTable
                            where salesParmSubTable.ParmId == parmIdLoc
                            && salesTable.SalesId == salesParmSubTable.OrigSalesId
            {
                // TODO ACTION
            }
            ttscommit;
           
        }
        next prepareProforma(_formletterJournalPost);
}
Deovandski Skibinski
Our Verified Expert
Deovandski Skibinski

Deovandski Skibinski is a developer with experience across multiple programming languages and recent expertise in X++ development for Dynamics 365 Finance and Supply Chain. He brings a passion for problem solving client challenges and enjoys sharing knowledge with fellow developers. His work spans retail, distribution, inventory, and finance, where he builds reliable, efficient solutions that help businesses run smarter. Deo holds a bachelor’s degree in Computer Science from North Dakota State University.

Read More from Deovandski Skibinski

Related Posts


Under the terms of this license, you are authorized to share and redistribute the content across various mediums, subject to adherence to the specified conditions: you must provide proper attribution to Stoneridge as the original creator in a manner that does not imply their endorsement of your use, the material is to be utilized solely for non-commercial purposes, and alterations, modifications, or derivative works based on the original material are strictly prohibited.

Responsibility rests with the licensee to ensure that their use of the material does not violate any other rights.

Start the Conversation

It’s our mission to help clients win. We’d love to talk to you about the right business solutions to help you achieve your goals.

Subscribe To Our Blog

Sign up to get periodic updates on the latest posts.

Thank you for subscribing!