How to upload an Excel file in ABAP¶
Read an
.xls/.xlsxfile from the user's PC into a typed internal table — step by step.
Introduction¶
Excel uploads come up constantly in SAP projects:
- Mass data entry — users maintain large lists (materials, vendors, cost assignments) in Excel and expect a one-click import instead of manual SM30/transaction entry.
- Data migrations — cutover data arrives as Excel sheets; a quick report loads it into staging tables before the formal migration tool runs.
- Interfaces — a downstream system or third party sends flat data in Excel because they have no API.
The standard approach uses ALSM_EXCEL_TO_INTERNAL_TABLE, which works on every classical ABAP system (ECC 6.0+, S/4HANA) without extra add-ons, as long as the user runs SAP GUI with Microsoft Excel installed on their PC.
This tutorial uses a dialog program
ALSM_EXCEL_TO_INTERNAL_TABLE opens Excel on the frontend via OLE. It cannot run in background jobs or on the application server directly. See Alternatives if you need server-side processing.
Sample file¶
Download the ready-made Excel file used in this tutorial and load it directly into the example program:
Prerequisites¶
- A dialog program (
REPORTor module pool) executed via SAP GUI — not a background job. - Microsoft Excel (or a compatible viewer such as LibreOffice with OLE support) installed on the user's PC.
RLGRAP-FILENAMEin scope — part of the standard ABAP dictionary; no extra includes required.- Authorization for the target business objects if you write data at the end (e.g.
BAPI_MATERIAL_SAVEDATAneeds MM authorizations).
Step 1 — Add a file-selection parameter to the selection screen¶
Give the user a text field for the file path and wire up the F4 help so they can browse to the file rather than typing a path manually.
PARAMETERS p_file TYPE rlgrap-filename OBLIGATORY.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file.
CALL FUNCTION 'F4_FILENAME'
EXPORTING
program_name = syst-repid
dynpro_number = syst-dynnr
field_name = 'P_FILE'
IMPORTING
file_name = p_file.
F4_FILENAME opens the standard Windows file-open dialog. The selected path is written back into p_file automatically.
OBLIGATORY keeps the report safe
Marking p_file as OBLIGATORY prevents execution with an empty path and produces a clean framework error message rather than a confusing dump.
Step 2 — Call ALSM_EXCEL_TO_INTERNAL_TABLE¶
Load the raw cell data from the file. The FM returns a flat table (ALSMEX_TABLINE) with one entry per non-empty cell: row number, column number, and value.
DATA lt_intern TYPE STANDARD TABLE OF alsmex_tabline.
CALL FUNCTION 'ALSM_EXCEL_TO_INTERNAL_TABLE'
EXPORTING
filename = p_file
i_begin_col = 1
i_begin_row = 2 " row 1 is the header — skip it
i_end_col = 3
i_end_row = 9999
TABLES
intern = lt_intern
EXCEPTIONS
inconsistent_parameters = 1
upload_ole = 2
OTHERS = 3.
IF sy-subrc <> 0.
MESSAGE 'Excel upload failed — check that Excel is installed and the file is not open.' TYPE 'E'.
ENDIF.
Key parameters to get right:
| Parameter | What to set | Why |
|---|---|---|
i_begin_row |
2 (or higher) |
Skip the header row; otherwise column labels end up as data |
i_end_col |
Number of columns in your sheet | Limits OLE reads; improves performance |
i_end_row |
9999 or a known max |
Reads to the last row; adjust downward for large-file performance |
For the full parameter reference see ALSM_EXCEL_TO_INTERNAL_TABLE.
Excel must be installed and not holding the file open
Exception UPLOAD_OLE (subrc 2) fires when OLE automation fails — most often because Excel is not installed, the file is already open and locked, or the file is corrupt. Check all three before assuming a code bug.
Step 3 — Pivot the flat cell table into a typed internal table¶
lt_intern gives you one row per cell. You need to reassemble it into one row per spreadsheet row. Loop over the cell table and use CASE ls_cell-col to route each value to the right field; append the completed row when you hit the last column (or a new row number).
TYPES: BEGIN OF ty_material,
matnr TYPE matnr,
maktx TYPE maktx,
meins TYPE meins,
END OF ty_material.
DATA: lt_mat TYPE STANDARD TABLE OF ty_material,
ls_mat TYPE ty_material.
LOOP AT lt_intern INTO DATA(ls_cell).
CASE ls_cell-col.
WHEN 1. ls_mat-matnr = ls_cell-value.
WHEN 2. ls_mat-maktx = ls_cell-value.
WHEN 3.
ls_mat-meins = ls_cell-value.
APPEND ls_mat TO lt_mat.
CLEAR ls_mat.
ENDCASE.
ENDLOOP.
Empty cells are not returned
ALSM_EXCEL_TO_INTERNAL_TABLE silently omits completely empty cells — they produce no entry in lt_intern. If column 2 is blank, the WHEN 2 branch never fires for that row. Appending on the last column (column 3 here) still works, but ls_mat-maktx will carry the value from the previous row. Always CLEAR ls_mat at the right moment and validate after pivoting.
Step 4 — Validate and process the rows¶
Never trust inbound data. Check mandatory fields, strip whitespace, and collect errors before touching the database.
DATA lt_errors TYPE STANDARD TABLE OF string.
LOOP AT lt_mat INTO ls_mat.
DATA(lv_line) = sy-tabix.
" Mandatory field check
IF ls_mat-matnr IS INITIAL.
APPEND |Row { lv_line }: MATNR is empty — skipped| TO lt_errors.
CONTINUE.
ENDIF.
IF ls_mat-meins IS INITIAL.
APPEND |Row { lv_line }: MEINS is empty — skipped| TO lt_errors.
CONTINUE.
ENDIF.
" Strip leading/trailing spaces Excel sometimes smuggles in
CONDENSE ls_mat-matnr.
CONDENSE ls_mat-maktx.
CONDENSE ls_mat-meins.
" --- your business logic here ---
" e.g. call BAPI_MATERIAL_SAVEDATA, INSERT INTO ztable, etc.
WRITE: / ls_mat-matnr, ls_mat-maktx, ls_mat-meins.
ENDLOOP.
" Report errors to the user
IF lt_errors IS NOT INITIAL.
WRITE: / 'Upload completed with warnings:'.
LOOP AT lt_errors INTO DATA(lv_err).
WRITE: / lv_err.
ENDLOOP.
ENDIF.
Full working example¶
A complete REPORT that ties all four steps together. Paste it into SE38, activate, and execute.
REPORT z_upload_material_list.
"--------------------------------------------------------------------
" Types
"--------------------------------------------------------------------
TYPES: BEGIN OF ty_material,
matnr TYPE matnr,
maktx TYPE maktx,
meins TYPE meins,
END OF ty_material.
"--------------------------------------------------------------------
" Data declarations
"--------------------------------------------------------------------
DATA: lt_intern TYPE STANDARD TABLE OF alsmex_tabline,
lt_mat TYPE STANDARD TABLE OF ty_material,
ls_mat TYPE ty_material,
lt_errors TYPE STANDARD TABLE OF string.
"--------------------------------------------------------------------
" Selection screen
"--------------------------------------------------------------------
PARAMETERS p_file TYPE rlgrap-filename OBLIGATORY.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file.
CALL FUNCTION 'F4_FILENAME'
EXPORTING
program_name = syst-repid
dynpro_number = syst-dynnr
field_name = 'P_FILE'
IMPORTING
file_name = p_file.
"--------------------------------------------------------------------
" Main processing
"--------------------------------------------------------------------
START-OF-SELECTION.
" Step 2 — load raw cell data from Excel
CALL FUNCTION 'ALSM_EXCEL_TO_INTERNAL_TABLE'
EXPORTING
filename = p_file
i_begin_col = 1
i_begin_row = 2
i_end_col = 3
i_end_row = 9999
TABLES
intern = lt_intern
EXCEPTIONS
inconsistent_parameters = 1
upload_ole = 2
OTHERS = 3.
IF sy-subrc <> 0.
MESSAGE 'Excel upload failed — check that Excel is installed and the file is not open.'
TYPE 'E'.
ENDIF.
" Step 3 — pivot flat cell table into structured rows
LOOP AT lt_intern INTO DATA(ls_cell).
CASE ls_cell-col.
WHEN 1. ls_mat-matnr = ls_cell-value.
WHEN 2. ls_mat-maktx = ls_cell-value.
WHEN 3.
ls_mat-meins = ls_cell-value.
APPEND ls_mat TO lt_mat.
CLEAR ls_mat.
ENDCASE.
ENDLOOP.
" Step 4 — validate and process
LOOP AT lt_mat INTO ls_mat.
DATA(lv_line) = sy-tabix.
IF ls_mat-matnr IS INITIAL.
APPEND |Row { lv_line }: MATNR is empty — skipped| TO lt_errors.
CONTINUE.
ENDIF.
IF ls_mat-meins IS INITIAL.
APPEND |Row { lv_line }: MEINS is empty — skipped| TO lt_errors.
CONTINUE.
ENDIF.
CONDENSE ls_mat-matnr.
CONDENSE ls_mat-maktx.
CONDENSE ls_mat-meins.
" Replace with your actual write logic (BAPI, direct INSERT, etc.)
WRITE: / ls_mat-matnr, ls_mat-maktx, ls_mat-meins.
ENDLOOP.
" Summary
WRITE: / '---'.
WRITE: / |{ lines( lt_mat ) } rows read, { lines( lt_errors ) } skipped.|.
IF lt_errors IS NOT INITIAL.
WRITE: / 'Skipped rows:'.
LOOP AT lt_errors INTO DATA(lv_err).
WRITE: / lv_err.
ENDLOOP.
ENDIF.
| A (MATNR) | B (MAKTX) | C (MEINS) |
|---|---|---|
| MATNR | MAKTX | MEINS |
| MAT-001 | Steel plate 10mm | KG |
| MAT-002 | Copper wire 2.5mm | M |
| MAT-003 | Bolt M8x20 | PC |
Row 1 is the header — i_begin_row = 2 skips it. Columns must be in the exact order shown; the CASE statement maps by column number, not by header name.
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
UPLOAD_OLE / subrc 2 |
Excel not installed, file locked, or corrupt file | Ensure Excel is installed on the frontend; close the file; check it opens manually |
| Values truncated at 50 chars | ALSMEX_TABLINE-VALUE is CHAR50 |
Use TEXT_CONVERT_XLS_TO_SAP or export the sheet as CSV and use GUI_UPLOAD |
| Missing data for some rows | Empty cells not returned by the FM | Add a guard in the pivot loop; CLEAR ls_mat at the start of each new row number rather than at the end |
| "File not found" despite correct path | Path contains non-ASCII characters or network share issues | Copy the file to a local drive (C:\Temp) and try again |
| Works in foreground, fails in batch | OLE requires a desktop session | Switch to TEXT_CONVERT_XLS_TO_SAP or pre-convert to CSV |
| Data from previous row bleeds into next | CLEAR ls_mat placed incorrectly |
Clear ls_mat when column 1 is encountered (start of a new row), not only after appending |
Background jobs are not supported
ALSM_EXCEL_TO_INTERNAL_TABLE uses OLE automation, which requires an interactive SAP GUI session on the user's desktop. Scheduling the program as a background job will always raise UPLOAD_OLE. Use TEXT_CONVERT_XLS_TO_SAP or a server-side file transfer for batch scenarios.
50-character truncation is silent
There is no exception, no return code, and no warning when a value is cut. If your data has long text fields (e.g. material descriptions longer than 50 chars), you will get wrong data without any error. Validate critical fields against their dictionary length after the upload.
Alternatives¶
| Scenario | Recommended approach |
|---|---|
| No Excel on frontend / background job | Save as CSV, upload with GUI_UPLOAD, parse with SPLIT |
| Large files (> 5,000 rows) | TEXT_CONVERT_XLS_TO_SAP — server-side, no OLE, handles wider values |
| Cell values > 50 characters | CSV via GUI_UPLOAD — no length limit |
| Web Dynpro / SAP Fiori | CL_FDT_XL_SPREADSHEET or BTP-based file upload |
| S/4HANA RAP / OData | Multipart file upload endpoint + XLSX parsing in ABAP Objects |
See also¶
ALSM_EXCEL_TO_INTERNAL_TABLE— full parameter reference and pitfallsTEXT_CONVERT_XLS_TO_SAP— alternative FM for server-side / large-file scenariosGUI_UPLOAD— upload any text or binary file from the frontendF4_FILENAME— the file-picker dialog used in Step 1- Transaction SE38 — create and test your report