7.2 XMLport Node Hierarchy, Triggers & Stream Manipulation

Key Takeaways

  • XMLport schema trees are composed of hierarchical nodes: tableelement (bound to table records), fieldelement (bound to table fields), textelement (unbound text variables), and attribute nodes.
  • Relational parent-child data structures are established using LinkTable and LinkFields properties on nested child tableelements, replicating SQL master-detail joins.
  • Database write operations during import are controlled by AutoSave, AutoUpdate, AutoReplace, and AutoValidating properties on tableelements.
  • The XMLport execution lifecycle executes root triggers (OnInitXMLport, OnPreXMLport, OnPostXMLport) and node triggers, enabling runtime flow control via currXMLport.Skip() and currXMLport.Break().
  • Headless programmatic execution in AL leverages Xmlport.Import and Xmlport.Export with InStream, OutStream, and the TempBlob codeunit for scalable cloud and API integrations.
Last updated: August 2026

7.2 Designing XMLport Nodes, Triggers & Stream Handling in AL

Designing robust XMLports requires a deep understanding of schema node hierarchies, database write mechanics, trigger execution sequences, and programmatic stream manipulation. In complex enterprise integrations, XMLports rarely perform raw table dumps; instead, they transform complex XML payloads, handle master-detail relational structures, apply custom business logic during ingestion, and stream data directly to and from cloud storage or external API endpoints.


1. Schema Node Hierarchy & Element Types

The schema block defines the layout and binding of the data stream. Every XMLport schema begins with a single root textelement, beneath which child nodes are declared.

schema
{
    textelement(RootNode)
    {
        tableelement(Header; "Sales Header")
        {
            AutoSave = true;
            AutoUpdate = false;
            AutoReplace = false;
            
            fieldelement(DocType; Header."Document Type") { }
            fieldelement(DocNo; Header."No.") { }
            fieldattribute(SellToCustomer; Header."Sell-to Customer No.") { }
            textelement(CalculatedTotal)
            {
                trigger OnBeforePassVariable()
                begin
                    CalculatedTotal := Format(Header."Amount Including VAT");
                end;
            }

            tableelement(Line; "Sales Line")
            {
                LinkTable = Header;
                LinkFields = "Document Type" = field("Document Type"),
                             "Document No." = field("No.");

                fieldelement(LineNo; Line."Line No.") { }
                fieldelement(ItemNo; Line."No.") { }
                fieldelement(Quantity; Line.Quantity) { }
                fieldelement(UnitPrice; Line."Unit Price") { }
            }
        }
    }
}

Schema Node Element Types

  1. tableelement: Binds directly to a Business Central table cursor. Represents a repeating record set during import or export.
  2. fieldelement: Binds a schema element directly to a field in the parent tableelement table.
  3. textelement: Declares an unbound string variable within the schema. Used to parse custom XML tags, calculate derived export strings, or hold temporary data for AL trigger processing.
  4. fieldattribute: Used in Format = Xml to serialize a table field as an XML attribute within the parent element's start tag (e.g., <Header SellToCustomer="C10000">).
  5. textattribute: Serializes an unbound AL variable as an XML attribute within the parent tag.

Critical tableelement Properties for Data Ingestion

PropertyTypeDefaultRuntime Ingestion Behavior
AutoSaveBooleantrueWhen true, the platform automatically writes the incoming record to the database table. If false, the platform parses fields into the table buffer, but developers must write explicit Rec.Insert(true) or Rec.Modify(true) AL code in node triggers.
AutoUpdateBooleanfalseWhen true, if a record with the same primary key already exists during import, the platform updates the existing record with imported values instead of throwing a primary key duplicate error.
AutoReplaceBooleanfalseWhen true, if an existing record is encountered, the platform deletes or replaces the existing record entirely with the imported record rather than performing an in-place field update.
LinkTableTable IdentifierNoneSpecifies the parent tableelement to which this child tableelement is relationally linked.
LinkFieldsField MappingNoneSpecifies the join conditions between child fields and parent fields (e.g., "Document No." = field("No.")).
MinOccursZero / OnceZeroSpecifies whether the element is optional (Zero) or mandatory (Once) in incoming XML schemas.
MaxOccursOnce / UnboundedUnboundedSpecifies whether the element may repeat (Unbounded) or occur at most once (Once).
Loading diagram...
XMLport Trigger Execution Lifecycle

2. The XMLport Trigger Execution Lifecycle

XMLport triggers execute at precise phases during stream processing. Understanding the sequence of trigger execution is crucial for implementing data transformations, conditional record filtering, and transaction control.

Root-Level Triggers

  1. OnInitXMLport: Executes once when the XMLport object is instantiated before the request page is initialized. Used to set default variable values and establish initial parameters.
  2. OnPreXMLport: Executes after the user dismisses the request page (or immediately upon startup if UseRequestPage = false), before the first element or stream byte is read/written. Used to initialize file headers, log integration audit entries, or dynamically apply table filters.
  3. OnPostXMLport: Executes once after all records, elements, and streams have been completely processed and written. Used for post-processing routines, sending confirmation telemetry, or cleaning up temporary data.

Node-Level Triggers on tableelement (Import Lifecycle)

  • OnAfterInitRecord: Fires immediately after a new empty table record buffer is initialized, before incoming field values are mapped. Ideal for populating default header/line values (e.g., setting "Document Type" := "Document Type"::Order).
  • OnBeforeInsertRecord: Fires after all fields have been populated and validated, immediately before the platform inserts the record into the database table (when AutoSave = true). Developers can modify field values or call currXMLport.Skip() to abort insertion of the current record without terminating the entire import batch.
  • OnAfterInsertRecord: Fires immediately after the record is successfully committed into the table.
  • OnBeforeModifyRecord: Fires when AutoUpdate = true and an existing record is about to be updated.
  • OnAfterModifyRecord: Fires after the modified record is updated in the database.

Node-Level Triggers on textelement & textattribute

  • OnBeforePassVariable (Export): Fires before an unbound text variable or attribute value is written into the export stream. Developers assign the desired formatted string value to the variable in this trigger.
  • OnAfterPassVariable (Import): Fires immediately after a text variable or attribute is read from the input stream. Developers write custom parsing logic or evaluate the text value to update global variables.
// Example: Skipping Invalid Records & Dynamic Parsing in Triggers
textelement(TransactionAmountText)
{
    trigger OnAfterPassVariable()
    var
        DecValue: Decimal;
    begin
        if not Evaluate(DecValue, TransactionAmountText) then
            currXMLport.Skip(); // Skip invalid line
        
        Header."Total Amount" := DecValue;
    end;
}

3. Programmatic XMLport Invocation & Stream Manipulation in AL

In modern cloud architectures, XMLports are rarely triggered by manual user clicks in the UI. Instead, they run programmatically within API handlers, Azure Blob storage synchronization routines, or Job Queue codeunits using InStream and OutStream objects.

Core Execution Methods

// 1. Interactive or Headless UI Execution
Xmlport.Run(Xmlport::"Export Customer CSV", true, false, CustomerRecord);

// 2. Headless InStream Ingestion
Xmlport.Import(Xmlport::"Import Bank Statement", InStreamVariable);

// 3. Headless OutStream Emission
Xmlport.Export(Xmlport::"Export PEPPOL Invoice", OutStreamVariable, SalesInvoiceHeaderRecord);

In-Memory Streaming Pattern with TempBlob

When exchanging data with web services, Azure Blob Storage, or Base64 payloads, developers use the TempBlob codeunit (from the System Application module) to create transient in-memory streams without creating temporary files on disk.

codeunit 50130 "Data Exchange Handler"
{
    procedure ExportCustomersToBlob(var TempBlob: Codeunit "TempBlob"; CustomerPostingGroupFilter: Code[20])
    var
        Customer: Record Customer;
        OutStr: OutStream;
    begin
        // Filter customer records
        if CustomerPostingGroupFilter <> '' then
            Customer.SetRange("Customer Posting Group", CustomerPostingGroupFilter);

        // Create OutStream targeting in-memory TempBlob
        TempBlob.CreateOutStream(OutStr, TextEncoding::UTF8);
        
        // Execute XMLport export directly into OutStream
        Xmlport.Export(Xmlport::"Export Customer CSV", OutStr, Customer);
    end;

    procedure ImportVendorsFromStream(InStr: InStream)
    var
        VendorXmlport: Xmlport "Import Vendor FlatFile";
    begin
        // Set InStream and run import headlessly
        VendorXmlport.SetSource(InStr);
        VendorXmlport.Import();
    end;
}

Cloud File Handling with DownloadFromStream & UploadIntoStream

In Business Central SaaS (cloud), the server has no direct access to client desktop file systems. All file operations must utilize streams and browser transfer dialogs:

procedure ExportAndDownloadCustomerCsv()
var
    TempBlob: Codeunit "TempBlob";
    InStr: InStream;
    OutStr: OutStream;
    FileName: Text;
begin
    FileName := 'Customers_' + Format(Today, 0, '<Year4><Month,2><Day,2>') + '.csv';
    TempBlob.CreateOutStream(OutStr, TextEncoding::UTF8);
    
    // Export XMLport to stream
    Xmlport.Export(Xmlport::"Export Customer CSV", OutStr);
    
    // Convert to InStream and trigger browser download
    TempBlob.CreateInStream(InStr);
    DownloadFromStream(InStr, 'Export Data', '', 'All Files (*.*)|*.*', FileName);
end;

Exam Watchout — currXMLport.Skip() vs. currXMLport.Break(): Calling currXMLport.Skip() in an import node trigger abandons processing for the current record and advances the stream to the next record. Calling currXMLport.Break() terminates the entire XMLport execution immediately, leaving previously committed records in the database.

Test Your Knowledge

An AL developer is designing an XMLport to import sales orders. When an incoming order header has a 'No.' that already exists in the database, the requirement states that the XMLport must modify the existing database record with the new incoming values rather than failing with a primary key collision error. Which property must be configured on the 'Sales Header' tableelement?

A
B
C
D
Test Your Knowledge

A developer needs to write AL code to execute an XMLport programmatically in the background without user interaction, ingesting data from an InStream variable. Which AL statement accomplishes this?

A
B
C
D
Test Your Knowledge

During the execution of an XMLport import on a Customer tableelement with AutoSave = true, an AL developer needs to evaluate external credit rating data and selectively prevent specific records from being inserted into the database without halting the import of subsequent records. In which trigger should the developer write this validation logic?

A
B
C
D
Test Your Knowledge

An AL developer is building a cloud-compliant export routine where an XMLport must export data to an in-memory buffer and then trigger a browser file download for the user. Which pattern correctly implements this workflow in Business Central SaaS?

A
B
C
D