Purpose
Extract data from a PDF via ChatPDF and populate a transaction in SAP

Solution (via ChatPDF)

  1. Upload the PDF to ChatPDF
  2. Make API calls to extract the data to GuiXT variables
  3. Verify and format the data
  4. Enter the data into an SAP transaction

Example

This guide shows how to extract structured data from a PDF and use it to populate an SAP transaction automatically. It walks through uploading a PDF, extracting order header fields and line items (using a PDF parsing/LLM service), validating and correcting the extracted data, and creating a sales order in transaction VA01. Example scripts and a downloadable project archive are included so you can run the process end-to-end.


Show video in full screen

Expand the transaction

The transaction UI is extended to support importing orders from PDFs. This adds a "Create from PDF" toolbar button, an embedded PDF viewer and a compact menu with actions (Clear, Check, Copy into VA01). Uploaded PDFs are parsed (the example uses a PDF parsing/LLM service), extracted header fields and line items are displayed in editable fields and a table, and validation tools let you review and correct data before creating the sales order. The examples below show the screen layout and the scripts that implement upload, parsing, validation and order creation.


GuiXT
// initial state, no PDF loaded
if not V[va01_pdf_url]
  PushButton (toolbar) "Create from PDF" process="va01_import_pdf.txt"  
else
  
  // Get language
  CreateTable V[va01_pdf_chk_lang] lang
  Set V[ltab] "T002"
  Set V[lcon] "LAISO = '&V[va01_pdf_lang]'"
  Set V[lfie] "*SPRAS"
  Call /guixt/dbselect cache="session" _
    export.table="ltab" _
    export.condition="lcon" _
    export.fields="lfie" _
    import.values="va01_pdf_chk_lang"
  if not V[va01_pdf_chk_lang.lang.1]
    Return "E: Unknown language code: &V[va01_pdf_lang]" -statusline
  endif
  
  // remove all standard elements
  del (0,0) (40,200)  
  
  Box (1,1) (32,100) "&V[va01_pdf_url]"
  WebView (2,2) (32,100) "&V[va01_pdf_url]" name="pdf_display" -closeOnHide
  
  PushButton (33,1) "Clear" process="va01_pdf_clear.txt" size=(2,20)
  PushButton (33,81) "Check" process="va01_pdf_check.txt" size=(2,20)
  
  Offset (0,102)
  Box	(1,1)	(32,100) "Order data"
  
  InputField (3,2) "Sold-To Account" (3,20) size=50 _
                name="va01_pdf_soldacct" techName="KUAGV-KUNNR"
  InputField (4,2) "Sold-To Party" (4,20) size=50 name="va01_pdf_soldto"
  
  InputField (6,2) "Ship-To Account" (6,20) size=50 _
                name="va01_pdf_shipacct" techName="KUWEV-KUNNR"
  InputField (7,2) "Ship-To Party" (7,20) size=50 name="va01_pdf_shipto"
  
  InputField (9,2) "Company name" (9,20) size=50 name="va01_pdf_name1"
  
  InputField (11,2) "Order number" (11,20) size=50 name="va01_pdf_ordernum"
  
  if not V[va01_pdf_itemstab]
    CreateTable V[va01_pdf_itemstab] id qty unit text
    Clear V[va01_pdf_itemstab.stat.*]
    
    // Remove empty rows and format LF to CRLF
    CopyText fromString="va01_pdf_items" toText="va01_pdf_items"
    ReplaceText "va01_pdf_items" fromHexCode="0A" toHexCode="0D"
    ReplaceText "va01_pdf_items" fromHexCode="0D0D" toHexCode="0D"
    ReplaceText "va01_pdf_items" fromHexCode="0D" toHexCode="0D0A"
    CopyText fromText="va01_pdf_items" toTable="V[va01_pdf_itemstab]"
  endif
  
  Table (13,2) (32,86) _
    title="&V[va01_pdf_itemstab.rowcount] ordered items" _
    name="va01_pdf_itemstab" _
    rows="&V[va01_pdf_itemstab.rowcount]" _
    fixedColumns=4 _
    -rowSelection
  Column "Article" size=20 name="id"
  Column "Quantity" size=10 name="qty"
  Column "Unit" size=8 name="unit"  
  Column "Text" size=40 name="text"  
  
  PushButton (33,1) "Copy data into VA01" process="va01_start.txt" _
                size=(2,20) 
endif

va01_pdf_clear.txt

GuiXT
// clear PDF url and data
Clear V[va01_pdf*]
DeleteTable V[va01_pdf_itemstab]

Return
Connect ChatPDF

To use ChatPDF, you must create an account and obtain an API key. The key authorizes calls to ChatPDF's REST API for uploading PDFs and issuing prompts to extract structured data. Do not hard-code the key in source control - store it securely (environment variable, protected config, or a GuiXT-secure setting). The example uses synchronous XHR because the GuiXT JavaScript bridge in this sample uses blocking calls that immediately return values to GUiXT. An asynchronous integration pattern to avoid blocking the UI would also be possible but  more complicated. Also review ChatPDF usage limits and any data privacy requirements before uploading sensitive documents.

PDFguixt.html - JavaScript content

JavaScript
const apiKey = "sec_xxxxxxYourAPIKeyHere";
const baseUrl = "https://api.chatpdf.com/v1/"

function readFileAsBinary(filePath) {
    try {
        var stream = guixthost.CreateObject("ADODB.Stream");
        stream.Type = 1;
        stream.Open();
        stream.LoadFromFile(filePath);
        var binaryData = stream.Read();
        stream.Close();
        return binaryData;
    } catch (e) {
        console.error("Error reading file:", e.message);
        return null;
    }
}

function uploadFile(filePath, fileContent) {
    var xhr = new XMLHttpRequest();
    var url = baseUrl + "sources/add-file";
    xhr.open("POST", url, false);

    var boundary = "----WebKitFormBoundary" + Date.now().toString(16);
    xhr.setRequestHeader("x-api-key", apiKey);
    xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" +
                boundary);

    var body = new Uint8Array(0);
    var fileName = filePath.split("\\").pop();

    function appendToBody(data) {
        var newBody = new Uint8Array(body.length + data.length);
        newBody.set(body);
        newBody.set(data, body.length);
        body = newBody;
    }

    var startBoundary = "--" + boundary + "\r\n" +
        'Content-Disposition: form-data; name="file"; filename="' +
                fileName + '"\r\n' +
        "Content-Type: application/octet-stream\r\n\r\n";
    appendToBody(new TextEncoder().encode(startBoundary));

    appendToBody(new Uint8Array(fileContent));

    var endBoundary = "\r\n--" + boundary + "--\r\n";
    appendToBody(new TextEncoder().encode(endBoundary));

    xhr.send(body);

    if (xhr.status === 200) {
        var data = JSON.parse(xhr.responseText);
        console.log("Source ID:", data.sourceId);
        return data.sourceId;
    } else {
        console.error("Upload failed: HTTP " + xhr.status + " - " +
                xhr.statusText);
        throw new Error("Upload failed: HTTP " + xhr.status + " - " +
                xhr.statusText);
    }
}

function saveSourceId(path, overwrite) {
    overwrite = overwrite || false;
    var sourceId = guixthost.Get("sourceid");
    if (overwrite || !sourceId) {
        sourceId = getSourceId(path);
        guixthost.Set("sourceid", sourceId);
    }
    return sourceId;
}

function getSourceId(path) {
    var fileContent = readFileAsBinary(path);
    if (fileContent) {
        return uploadFile(path, fileContent);
    } else {
        console.error("Failed to read file:", path);
        throw new Error("Failed to read file: " + path);
    }
}

function sendMessageAndSave(sourceId, message, varname, overwrite) {
    overwrite = overwrite || false;
    if (!overwrite) {
        var variable = guixthost.Get(varname);
        if (variable) return variable;
    }
    var result = sendMessageToChatPDF(sourceId, message);
    guixthost.Set(varname, result);
    return result;
}

function sendMessageToChatPDF(sourceId, message) {
    var xhr = new XMLHttpRequest();
    var url = baseUrl + "chats/message";
    xhr.open("POST", url, false);

    xhr.setRequestHeader("x-api-key", apiKey);
    xhr.setRequestHeader("Content-Type", "application/json");

    var requestBody = JSON.stringify({
        sourceId: sourceId,
        messages: [
            {
                role: "user",
                content: message,
            },
        ],
    });

    xhr.send(requestBody);

    if (xhr.status === 200) {
        var data = JSON.parse(xhr.responseText);
        return data.content;
    } else {
        console.error("Error: HTTP " + xhr.status + " - " +
                xhr.statusText);
        throw new Error("Error: HTTP " + xhr.status + " - " +
                xhr.statusText);
    }
}
Import data from a PDF

The JavaScript helper uploads the PDF (using getSourceId()) and then issues a series of prompts to ChatPDF to extract specific fields. Because ChatPDF is a language model, every prompt must explicitly request a machine-friendly output (no explanatory text or confirmations). For example: ask for a single two-letter ISO language code, or ask for "ID, quantity, unit and description of each item - return only the values, row by row, and separate each value with a single LF character; do not include any labels or commentary." Strict, repeatable formatting makes parsing reliable and keeps post-processing simple. Always inspect and validate extracted values before creating the order; the example shows basic validation and correction steps you can extend for your data quality requirements.

va01_import_pdf.txt

GuiXT
SelectFile name="va01_pdf_url" filter="*.pdf" directory="F:\orders"

if not V[va01_pdf_url]
  Return "E: No file selected!"
endif

StatusMessage title="PDF Import" addString="Uploading file..."

// read pdf
CallJS va01_pdf_sourceid = PDFguixt.getSourceId "&V[va01_pdf_url]"
StatusMessage addString="Acquired source ID"

CallJS va01_pdf_lang = PDFguixt.sendMessageToChatPDF _
                "&V[va01_pdf_sourceid]" _
                "What is the language of the item descriptions?
                Only include the uppercase two letter
                ISO-639 code of the language in your reply."
CallJS va01_pdf_soldacct = PDFguixt.sendMessageToChatPDF _
                "&V[va01_pdf_sourceid]" _
                "What is the account number of the customer the order is to
                be sold to? Only include the number without line breaks in
                your reply."
CallJS va01_pdf_shipacct = PDFguixt.sendMessageToChatPDF _
                "&V[va01_pdf_sourceid]" _
                "What is the account number of the customer the order is to
                be shipped to? Only include the number without line breaks
                in your reply."
CallJS va01_pdf_soldto = PDFguixt.sendMessageToChatPDF _
                "&V[va01_pdf_sourceid]" _
                "What is the address the order is to be sold to? Only
                include the address without line breaks in your reply."
CallJS va01_pdf_shipto = PDFguixt.sendMessageToChatPDF _
                "&V[va01_pdf_sourceid]" _
                "What is the address the order is to be shipped to? Only
                include the address without line breaks in your reply."
CallJS va01_pdf_name1 = PDFguixt.sendMessageToChatPDF _
                "&V[va01_pdf_sourceid]" _
                "What is the name of the company? Only include the name
                without line breaks in your reply."
CallJS va01_pdf_ordernum = PDFguixt.sendMessageToChatPDF _
                "&V[va01_pdf_sourceid]" _
                "What is the order number? Only include the number without
                line breaks in your reply."
CallJS va01_pdf_cref = PDFguixt.sendMessageToChatPDF _
                "&V[va01_pdf_sourceid]" _
                "What is the customer reference? Only include the reference
                without line breaks in your reply."
StatusMessage addString="Acquired singular data"
CallJS va01_pdf_items = PDFguixt.sendMessageToChatPDF _
                "&V[va01_pdf_sourceid]" _
                "Return the ID, quantity, unit and description of each item,
                listing only the values, row by row, separating each value
                with only one LF character. Treat unit and quantity as
                individual values."
StatusMessage -remove

Return
Populate transaction

After verifying and, if needed, correcting the extracted values, the script writes them into a new sales order. The example defaults to order type "TA" and can prompt the user for any missing inputs (for example, sales organization). When adding line items the script uses GuiXT's Screen and Enter "/scrollToLine=..." to automatically scroll the item table as rows are written. Before enabling unattended creation, extend the validation step to include required-field checks, item existence, unit conversion and any company-specific business rules.


va01_start.txt

GuiXT
// Define table name for simplicity
Set V[va01_pdf_tabname] "SAPMV45A_TCTRL_U_ERF_AUFTRAG"

// Use TA as default
Set [RV45A-TXT_AUART] "TA"

// Switch to order data and write to fields
Screen SAPMV45A.4001
  Set [KUAGV-KUNNR] "&V[va01_pdf_soldacct]"
  Set [KUWEV-KUNNR] "&V[va01_pdf_shipacct]"
  Set [VBKD-BSTKD] "&V[va01_pdf_cref]"
  
  // Write items to table 
  Set V[va01_pdf_abs_item] 1
  Enter P[T\02]
  
  label new_screen
Screen SAPMV45A.4001
  GetTableAttribute T[&V[va01_pdf_tabname]] _
    lastVisibleRow="va01_pdf_lv" _
    firstVisibleRow="va01_pdf_abs_item"
  
  Set V[va01_pdf_wrt_item] 1
  
  label wrt_item 
  if V[va01_pdf_abs_item>&V[va01_pdf_itemstab.rowcount]]  
    goto end
  endif
  // Scroll down if end of writable area reached
  if V[va01_pdf_abs_item>&V[va01_pdf_lv]] 
    Enter "/scrollToLine=&V[va01_pdf_abs_item]" _
                table="T[&V[va01_pdf_tabname]]"    
    goto new_screen
  endif   
  // Go to next empty row
  if not cell[&V[va01_pdf_tabname],RV45A-MABNR,&V[va01_pdf_wrt_item]=]
    Set V[va01_pdf_abs_item] "&V[va01_pdf_abs_item]" + 1
    Set V[va01_pdf_wrt_item] "&V[va01_pdf_wrt_item]" + 1
    goto wrt_item
  endif
  
  // Translate unit
  CreateTable V[va01_pdf_unit] unit
  Set V[table] "T006A"
  Set V[condition] "MSEHI = ( SELECT MSEHI FROM T006A WHERE SPRAS =
                '&V[va01_pdf_chk_lang.lang.1]' AND MSEH3 =
                '&V[va01_pdf_itemstab.unit.&V[va01_pdf_abs_item]]' )
                AND SPRAS = '&V[_language]'"
  Set V[fields] "MSEH3"
  
  // select data from table T006A
  Call /guixt/dbselect cache="session" _
    export.table="table" _
    export.condition="condition" _
    export.fields="fields" _
    import.values="va01_pdf_unit"
  
  // write row
  Set cell[&V[va01_pdf_tabname],RV45A-MABNR,&V[va01_pdf_wrt_item]] _
                "&V[va01_pdf_itemstab.id.&V[va01_pdf_abs_item]]"
  Set cell[&V[va01_pdf_tabname],VBAP-ARKTX,&V[va01_pdf_wrt_item]] _
                "&V[va01_pdf_itemstab.text.&V[va01_pdf_abs_item]]"
  Set cell[&V[va01_pdf_tabname],RV45A-KWMENG,&V[va01_pdf_wrt_item]] _
                "&V[va01_pdf_itemstab.qty.&V[va01_pdf_abs_item]]"
  Set cell[&V[va01_pdf_tabname],VBAP-VRKME,&V[va01_pdf_wrt_item]] _
                "&V[va01_pdf_unit.unit.1]"
  
  Set V[va01_pdf_abs_item] "&V[va01_pdf_abs_item]" + 1
  Set V[va01_pdf_wrt_item] "&V[va01_pdf_wrt_item]" + 1  
  goto wrt_item
  
  label end
  // scroll table to first line
  Enter "/scrollToLine=1" table="T[&V[va01_pdf_tabname]]"
Verify imported data

After importing, validate all extracted values before creating the order. Typical checks include verifying the sold‑to/ship‑to accounts and company, confirming each item ID exists in the master data, comparing extracted item descriptions to the stored names (flagging mismatches), and validating or converting units of measure. The example marks problematic rows for manual correction; for unattended processing extend the checks to include strict required‑field validation, automated mappings, and approval rules. Always show clear error messages and a summary of validation results before writing the sales order.


va01_pdf_check.txt

GuiXT
Set V[va01_pdf_errcnt] 0
Clear V[va01_pdf_itemstab.stat.rowselection.*]

// Check name
if V[va01_pdf_name1=guixt]
  Set V[va01_pdf_errcnt] "&V[va01_pdf_errcnt]" + 1
  StatusMessage addString="E: Invalid company name!" _
                title="&V[va01_pdf_errcnt] errors detected!"
endif

// Check items
Set V[va01_pdf_chk_item] 1
label chk_item
Set V[va01_pdf_chk_inr] "&V[va01_pdf_itemstab.id.&V[va01_pdf_chk_item]]"
Set V[va01_pdf_chk_mkt] "&V[va01_pdf_itemstab.text.&V[va01_pdf_chk_item]]"
Set V[va01_pdf_chk_unt] "&V[va01_pdf_itemstab.unit.&V[va01_pdf_chk_item]]"

// Check if item exists
CreateTable V[va01_pdf_chk_mat] matnr maktx meins

Set V[table] "MAKT JOIN MARA ON MAKT~MATNR = MARA~MATNR"
Set V[condition] "MARA~MATNR = @MATNR AND MAKT~SPRAS =
                '&V[va01_pdf_chk_lang.lang.1]'"
Set V[domname] "MATNR"
Set V[domvalue] "&V[va01_pdf_chk_inr]"
Set V[fields] "MARA~MATNR, MAKT~MAKTX, MARA~MEINS"

// Select data from tables MAKT and MARA
Call /guixt/dbselect cache="session" _
  export.table="table" _
  export.condition="condition" _
  export.fields="fields" _
  export.domname1="domname" _
  export.domvalue1="va01_pdf_chk_inr" _
  import.values="va01_pdf_chk_mat"

// Perform item existence and coherence checks
if not V[va01_pdf_chk_mat.matnr.1=&V[va01_pdf_chk_inr]]
  Set V[va01_pdf_errcnt] "&V[va01_pdf_errcnt]" + 1
  Set V[va01_pdf_itemstab.stat.rowselection.&V[va01_pdf_chk_item]] "X"
  StatusMessage addString="E: Item &V[va01_pdf_chk_inr] does not exist!"
                title="&V[va01_pdf_errcnt] errors detected!"
else
  // Check if the item description deviates from the stored description
  if V[va01_pdf_chk_lang.lang.1]
                and not V[va01_pdf_chk_mat.maktx.1=&V[va01_pdf_chk_mkt]]
    Set V[va01_pdf_errcnt] "&V[va01_pdf_errcnt]" + 1
    Set V[va01_pdf_itemstab.stat.rowselection.&V[va01_pdf_chk_item]] "X"
    StatusMessage addString="E: Name difference on item &V[va01_pdf_chk_inr]:
                Expected &V[va01_pdf_chk_mat.maktx.1],
                got &V[va01_pdf_chk_mkt]"
                title="&V[va01_pdf_errcnt] errors detected!" size=(0,90)
  endif
  // Translate unit
  CreateTable V[va01_pdf_chk_unit] unit
  Set V[table] "T006A"
  Set V[condition] "MSEHI = ( SELECT MSEHI FROM T006A WHERE SPRAS =
                '&V[va01_pdf_chk_lang.lang.1]'
                AND MSEH3 = '&V[va01_pdf_chk_unt]' )
                AND SPRAS = '&V[_language]'"
  Set V[fields] "MSEH3"
  
  // select data from table T006A
  Call /guixt/dbselect cache="session" _
    export.table="table" _
    export.condition="condition" _
    export.fields="fields" _
    import.values="va01_pdf_chk_unit"
  // Check if the item unit deviates from the stored unit
  if not V[va01_pdf_chk_mat.meins.1=&V[va01_pdf_chk_unit.unit.1]]
    Set V[va01_pdf_errcnt] "&V[va01_pdf_errcnt]" + 1
    Set V[va01_pdf_itemstab.stat.rowselection.&V[va01_pdf_chk_item]] "X"
    StatusMessage addString="E: Unit difference on item
                &V[va01_pdf_chk_inr]:
                Expected &V[va01_pdf_chk_mat.meins.1],
                got &V[va01_pdf_chk_unit.unit.1]"
                title="&V[va01_pdf_errcnt] errors detected!" size=(0,90)
  endif
endif
Set V[va01_pdf_chk_item] "&V[va01_pdf_chk_item]" + 1
if not V[va01_pdf_chk_item>&V[va01_pdf_itemstab.rowcount]]
  goto chk_item
endif

Return "W: Check finished! &V[va01_pdf_errcnt] errors found." -statusline

Project files "import_pdf.zip"