A frequent business requirement involves enabling mass changes to business objects via Excel uploads executed through a custom action popup. Historically, achieving this functionality has necessitated various workarounds, often involving UI5 extensions, third-party solutions, or Object Page manipulations, all of which present specific implementation challenges.
The existing workaround approaches present several drawbacks:
- Custom UI Extensions: Require specialized UI5 development expertise.
- Third-Party Solutions: Introduce risks related to licensing compliance and potential security vulnerabilities.
- Object Page Manipulations: Involve complex, multi-step processes, such as creating a dummy object page, facilitating file upload, temporarily storing the file data in a table field, and requiring a final user action (a button press) to initiate processing. This temporary data storage is often unnecessary, complicating the data model.
However, SAP has recently introduced ABAP / CAP annotations that offer a cloud-ready solution, potentially eliminating approximately 95% of the development effort typically associated with integrating an Excel upload into the backend. This innovation allows developers to prioritize implementing core business logic over developing reusable technical artifacts.
I will now detail the implementation steps.
A business requirement to manage mass processing listings for a library was selected to demonstrate this use case. The implementation requires several steps, with steps 3 through 6 being the special or additional configurations needed, while all others are considered routine.

Implementation Steps
1. A database table for the listing entity is created. This involves fields such as Id, Title, Type, and Author.
@EndUserText.label : 'Library Listings'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zrk_lib_listings {
key client : abap.clnt not null;
key listing_uuid : sysuuid_x16 not null;
id : abap.numc(10);
title : abap.char(40);
type : abap.char(5);
author : abap.char(40);
publisher_studio : abap.char(40);
isbn_ean : abap.char(40);
language_code : abap.lang;
publication_year : abap.numc(4);
description : abap.char(40);
totalcopies : abap.int2;
available_copies : abap.int2;
location_shelf_id : abap.char(40);
lending_duration_days : abap.int2;
status : abap.char(40);
cover_image_url : abap.char(100);
local_created_by : abp_creation_user;
local_created_at : abp_creation_tstmpl;
local_last_changed_by : abp_locinst_lastchange_user;
local_last_changed_at : abp_locinst_lastchange_tstmpl;
last_changed_at : abp_lastchange_tstmpl;
}
2. A RAP Business Object (BO) is generated, followed by the requisite UI artifacts. The specific RAP BO scenario (Managed, Unmanaged, Draft, or Non-Draft) is noted as not influencing the core Excel upload use case. The RAP Generator is used to simplify the demonstration.

3. A root abstract entity is created for the file to be uploaded. (This entity is highly reusable and can be applied across different RAP BOs).
@EndUserText.label: 'Abs. Entity For Attachment'
define root abstract entity ZRK_D_FILE_STREAM
{
@Semantics.largeObject.mimeType: 'MimeType'
@Semantics.largeObject.fileName: 'FileName'
@Semantics.largeObject.contentDispositionPreference: #INLINE
@EndUserText.label: 'Select Excel file'
StreamProperty : abap.rawstring(0);
.hidden: true
MimeType : abap.char(128);
.hidden: true
FileName : abap.char(128);
}
4. The abstract behavior definition for the file entity is implemented.
abstract;
strict(2);
with hierarchy;
define behavior for ZRK_D_FILE_STREAM {
}
5. A second abstract entity is created to serve as an action parameter. This entity includes an association to the file abstract entity (from Step 3).
@EndUserText.label: 'Action Param for Uploading Excel'
define root abstract entity ZRK_D_UPLOAD_EXCEL
{
// Dummy is a dummy field
@UI.hidden: true
dummy : abap_boolean;
_StreamProperties : association [1] to ZRK_D_FILE_STREAM on 1 = 1;
}
6. The abstract behavior definition for the action parameter is implemented, including the association to the earlier entity.
abstract;
strict ( 2 );
with hierarchy;
define behavior for ZRK_D_UPLOAD_EXCEL //alias <alias_name>
{
association _StreamProperties with hierarchy;
}
7. An action is defined on the RAP BO Behavior definition, with the parameter specified in Step 5.
static action ExcelUpload deep parameter ZRK_D_UPLOAD_EXCEL ;
managed implementation in class ZRK_BP_R_LIB_LISTINGS unique;
strict ( 2 );
with draft;
extensible;
define behavior for ZRK_R_LIB_LISTINGS alias Listings
persistent table ZRK_LIB_LISTINGS
extensible
draft table ZRK_LIB_LSTNGS_D
etag master LocalLastChangedAt
lock master total etag LastChangedAt
authorization master( global )
{
field ( readonly )
ListingUUID,
LocalCreatedBy,
LocalCreatedAt,
LocalLastChangedBy,
LocalLastChangedAt,
LastChangedAt;
field ( numbering : managed )
ListingUUID;
create;
update;
delete;
draft action Activate optimized;
draft action Discard;
draft action Edit;
draft action Resume;
draft determine action Prepare;
static action ExcelUpload deep parameter ZRK_D_UPLOAD_EXCEL ;
mapping for ZRK_LIB_LISTINGS corresponding extensible
{
ListingUUID = listing_uuid;
ID = id;
Title = title;
Type = type;
Author = author;
PublisherStudio = publisher_studio;
IsbnEan = isbn_ean;
LanguageCode = language_code;
PublicationYear = publication_year;
Description = description;
Totalcopies = totalcopies;
AvailableCopies = available_copies;
LocationShelfID = location_shelf_id;
LendingDurationDays = lending_duration_days;
Status = status;
CoverImageUrl = cover_image_url;
LocalCreatedBy = local_created_by;
LocalCreatedAt = local_created_at;
LocalLastChangedBy = local_last_changed_by;
LocalLastChangedAt = local_last_changed_at;
LastChangedAt = last_changed_at;
}
}
8. The business logic is implemented to read the Excel content. A released API, XCO_CP_XLSX , is used for this demonstration.
METHOD ExcelUpload.
TYPES : BEGIN OF ty_sheet_data,
id TYPE zrk_r_lib_listings-id,
title TYPE zrk_r_lib_listings-title,
type TYPE zrk_r_lib_listings-Type,
author TYPE zrk_r_lib_listings-author,
PublisherStudio TYPE zrk_r_lib_listings-PublisherStudio,
IsbnEan TYPE zrk_r_lib_listings-IsbnEan,
LanguageCode TYPE zrk_r_lib_listings-LanguageCode,
PublicationYear TYPE zrk_r_lib_listings-PublicationYear,
description TYPE zrk_r_lib_listings-Description,
Totalcopies TYPE zrk_r_lib_listings-Totalcopies,
AvailableCopies TYPE zrk_r_lib_listings-AvailableCopies,
LocationShelfID TYPE zrk_r_lib_listings-LocationShelfID,
LendingDurationDays TYPE zrk_r_lib_listings-LendingDurationDays,
status TYPE zrk_r_lib_listings-Status,
END OF ty_sheet_data.
DATA lv_file_content TYPE xstring.
DATA lt_sheet_data TYPE STANDARD TABLE OF ty_sheet_data.
DATA lt_listing_create TYPE TABLE FOR CREATE zrk_r_lib_listings.
lv_file_content = VALUE #( keys[ 1 ]-%param-_streamproperties-StreamProperty OPTIONAL ).
" Error handling in case file content is initial
DATA(lo_document) = xco_cp_xlsx=>document->for_file_content( lv_file_content )->read_access( ).
DATA(lo_worksheet) = lo_document->get_workbook( )->worksheet->at_position( 1 ).
DATA(o_sel_pattern) = xco_cp_xlsx_selection=>pattern_builder->simple_from_to(
)->from_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( 'A' ) " Start reading from Column A
)->to_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( 'N' ) " End reading at Column N
)->from_row( xco_cp_xlsx=>coordinate->for_numeric_value( 2 ) " *** Start reading from ROW 2 to skip the header ***
)->get_pattern( ).
lo_worksheet->select( o_sel_pattern
)->row_stream(
)->operation->write_to( REF #( lt_sheet_data )
)->set_value_transformation(
xco_cp_xlsx_read_access=>value_transformation->string_value
)->execute( ).
lt_listing_create = CORRESPONDING #( lt_sheet_data ).
MODIFY ENTITIES OF zrk_r_lib_listings IN LOCAL MODE
ENTITY Listings
CREATE AUTO FILL CID FIELDS ( Id Title Type author PublisherStudio IsbnEan LanguageCode PublicationYear description Totalcopies AvailableCopies LocationShelfID LendingDurationDays status )
WITH lt_listing_create
" TODO: variable is assigned but never used (ABAP cleaner)
MAPPED DATA(lt_mapped)
" TODO: variable is assigned but never used (ABAP cleaner)
REPORTED DATA(lt_reported)
" TODO: variable is assigned but never used (ABAP cleaner)
FAILED DATA(lt_failed).
" Communicate the messages to UI - not in scope of this demo
IF lt_failed IS INITIAL.
APPEND VALUE #( %msg = new_message_with_text( severity = if_abap_behv_message=>severity-success
text = 'Listings have been uploaded - please refresh the list!!' ) )
TO reported-listings.
ENDIF.
ENDMETHOD.
9. The action is utilized on the projection behavior and subsequently exposed in the metadata extension.
use action ExcelUpload;
projection implementation in class ZRK_BP_C_LIB_LISTINGS unique;
strict ( 2 );
extensible;
use draft;
use side effects;
define behavior for ZRK_C_LIB_LISTINGS alias Listings
extensible
use etag
{
use create;
use update;
use delete;
use action Edit;
use action Activate;
use action Discard;
use action Resume;
use action Prepare;
use action ExcelUpload;
}
.lineItem: [{ type:#FOR_ACTION , dataAction: 'ExcelUpload' , label: 'Upload Excel' }]
10. The service binding is published, and the application is then ready for execution.





Note:
This feature is currently functional on the BTP ABAP Environment. However, an issue appears to exist with metadata generation on S/4HANA 2023 On-Premise deployments, even though the objects are syntactically correct. It is anticipated that this constraint will be addressed in the S/4HANA 2025 release, making the full feature set available on the S/4HANA On-Premise version following a brief waiting period.