In SAP RAP (RESTful ABAP Programming), metadata annotations help define actions, UI behavior, and business logic exposure. But one limitation stands out: RAP does not allow a Download or Extract button to be created using annotations alone. This becomes a challenge when applications need to deliver documents, PDFs, or generated files directly to the end user within a Fiori interface.
So how do we solve this?
We solve this limitation by introducing a custom static action in SAP RAP to prepare the handles the user interaction, triggers the backend logic, and finally converts the returned content into a downloadable file. This approach allows us to deliver a clean, native-like download experience even though annotations alone cannot achieve it.
Detailed Implementation Steps
Below are all the implementation steps and code needed to enable file downloading in a SAP RAP-based Fiori app.
1. Creating an Abstract Entity for Download Parameters
This abstract entity acts as a structured container used to pass file metadata and content back from the RAP action to the UI5 layer.
@EndUserText.label: 'ADS - Media'
define abstract entity ZABS_ADS_MEDIA
{
mimeType : abap.string(0);
fileName: abap.string(0);
fileContent: abap.rawstring(0);
fileExtension: abap.string(0);
}
2. Adding a Static Action in the RAP Behavior Definition
A static action called Download is added in the behavior definition. This action returns the abstract entity defined above.
static action Download result[1] ZABS_ADS_MEDIA;use action Download;METHOD Download.
result = VALUE #(
FOR key IN keys
(
%cid = key-%cid
%param = VALUE #(
fileContent = '48656C6C6F3420476F7069'
fileExtension = '.txt'
fileName = 'Mytxtfile'
mimeType = 'text/plain'
)
)
).
ENDMETHOD.3. Building the Fiori App with TypeScript Enabled
While generating the Fiori Elements application using SAP Fiori Tools, make sure to enable TypeScript support. This ensures that the extension controller you create later benefits from strong typing, better error detection, and cleaner code structure. During the project creation wizard, simply select the TypeScript option before finishing the setup.






4. SAP UI5 Extension Controller for the Download Action
The download trigger is implemented in a UI5 extension controller. This controller invokes the RAP static action, retrieves the file data, converts the base64 content into a Blob, and triggers a browser download.
Extension Controller Code:
import ExtensionAPI from 'sap/fe/core/ExtensionAPI';
import Context from 'sap/ui/model/odata/v4/Context';
import ListReportExtensionAPI from 'sap/fe/templates/ListReport/ExtensionAPI';
import ODataContextBinding from 'sap/ui/model/odata/v4/ODataContextBinding';
import MessageBox from 'sap/m/MessageBox';
interface Error {
message: string;
error: {
code: string;
details: Error[];
};
}
interface Result {
fileContent: string;
fileName: string;
mimeType: string;
fileExtension: string;
}
const namespace = "com.sap.gateway.srvd.zsd_epcur_convert.v0001.";
export function DownloadActionFunction(this: ExtensionAPI, context: Context | undefined, selectedContexts: Context[]) {
const ListReportExtensionAPI = this as ListReportExtensionAPI;
const contexts = ListReportExtensionAPI.getSelectedContexts();
if (!contexts || contexts.length === 0) {
MessageBox.error("Please select one item to download.");
return;
}
if (contexts.length > 1) {
MessageBox.error("You can only select one item at a time for download.");
return;
}
const model = this.getModel();
if (!model) {
MessageBox.error("Data model not available.");
return;
}
const operation = model.bindContext("/EPCurConversion/" + namespace + "Download(...)") as ODataContextBinding;
if (!operation) {
MessageBox.error("Failed to bind the download operation.");
return;
}
const fnSuccess = () => {
const result = operation.getBoundContext()?.getObject() as Result;
if (!result || !result.fileName || !result.fileContent || !result.mimeType) {
MessageBox.error("Download failed: Invalid result returned from server.");
MessageBox.error(result.fileName);
return;
}
try {
let standardBase64 = result.fileContent
.replace(/_/g, '/')
.replace(/-/g, '+');
const byteCharacters = atob(standardBase64);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: result.mimeType });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = result.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
MessageBox.success(`File "${result.fileName}" downloaded successfully.`);
} catch (e) {
MessageBox.error("Error while saving the file.");
console.error(e);
}
};
const fnError = (error: Error) => {
const errorMessage = error?.message || "Unknown error occurred during download.";
MessageBox.error(errorMessage);
};
operation.invoke().then(fnSuccess, fnError);
}
Finding the Namespace:
The namespace used during the function call can be found inside annotation.xml.

Final Result
This implementation successfully adds a fully functional download feature inside a Fiori Elements List Report. The process feels native, efficient, and aligned with RAP’s architectural guidelines.
What you can expect:
- A clean Download button in your Fiori app
- RAP action generates and returns the file
- UI5 converts base64 to Blob and triggers download automatically
- Works for PDFs, text files, XML, CSV, and more

Key Benefits
This approach enhances RAP-based applications by introducing controlled, reliable file delivery directly from the backend
- Overcomes RAP’s annotation limitations
- Supports any file type
- Secure backend-governed file generation
- Flexible for business-specific formats
- Seamlessly integrates into Fiori Elements UX

