Skip to content

How to upload an Excel file in ABAP

Read an .xls / .xlsx file 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:

sample-material-upload.xlsx


Prerequisites

  • A dialog program (REPORT or 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-FILENAME in 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_SAVEDATA needs 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

Comments