Install
$ agentstack add skill-efeumutaslan-sap-skills-sap-ui5-fiori ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
SAPUI5 and Fiori Application Development
Related Skills
sap-fiori-testing— OPA5, wdi5, accessibility testing for the apps built heresap-odata— OData v2/v4 service design and consumption (the backend for most Fiori apps)sap-rap-comprehensive— RAP backends that auto-generate OData for UI5 consumptionsap-cap-advanced— CAP backends with CDS-based OData servicessap-build-apps— Low-code alternative when full UI5 is overkill
Quick Start
Scaffold a new freestyle UI5 app (Fiori Tools):
npm install -g @sap/generator-fiori
yo @sap/fiori
# Select: SAPUI5 freestyle → Basic → OData V4 service → project details
Scaffold a Fiori Elements app (zero-code UI):
yo @sap/fiori
# Select: SAPUI5 Fiori Elements → List Report Object Page (LROP)
Run locally with live-reload:
npm start # Starts ui5 serve with local OData mock or proxy
Build for deployment:
npm run build # Produces dist/ folder
npm run deploy # Deploys to ABAP, CF, or HTML5 repo
Core Concepts
MVC architecture
app/
├── webapp/
│ ├── Component.js # Component: root, manifest, models, router
│ ├── manifest.json # App descriptor (routes, models, data sources)
│ ├── view/ # XML views (declarative UI)
│ │ ├── App.view.xml
│ │ ├── Worklist.view.xml
│ │ └── Object.view.xml
│ ├── controller/ # Controller logic (event handlers, business logic)
│ │ ├── App.controller.js
│ │ ├── Worklist.controller.js
│ │ └── Object.controller.js
│ ├── model/ # Formatters, grouping, custom models
│ │ └── formatter.js
│ ├── i18n/ # Translations
│ │ └── i18n.properties
│ ├── css/ # Custom styles
│ └── test/ # QUnit/OPA5 tests
├── ui5.yaml # UI5 tooling config
└── package.json
Models (critical to understand)
| Model | Use case | Binding syntax | |-------|----------|----------------| | JSONModel | Client-side state, mock data, view state | {/path/to/value} | | ODataModel (v4) | Primary for SAPUI5 + modern SAP backends | {propertyName} or {BindingPath} | | ODataModel (v2) | Legacy OData services, Gateway services | {propertyName} | | ResourceModel | i18n texts from .properties files | {i18n>textKey} | | DeviceModel | Device info (phone/tablet/desktop, orientation) | {device>/system/phone} |
Binding modes
100 ? 'Expensive' : 'Cheap' }" />
buttonSave}" />
Routing (manifest.json)
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "my.app.view",
"controlId": "app",
"controlAggregation": "pages"
},
"routes": [
{ "name": "worklist", "pattern": "", "target": "worklist" },
{ "name": "object", "pattern": "Products/{productId}", "target": "object" }
],
"targets": {
"worklist": { "viewName": "Worklist" },
"object": { "viewName": "Object" }
}
}
Navigate from a controller:
this.getOwnerComponent().getRouter().navTo("object", { productId: "HT-1001" });
Common Patterns
Pattern 1: Basic controller skeleton
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/m/MessageToast"
], function (Controller, JSONModel, MessageToast) {
"use strict";
return Controller.extend("my.app.controller.Worklist", {
onInit: function () {
const oViewModel = new JSONModel({ busy: false, delay: 0 });
this.getView().setModel(oViewModel, "viewModel");
},
onRefresh: function () {
const oList = this.byId("productsList");
oList.getBinding("items").refresh();
},
onItemPress: function (oEvent) {
const sPath = oEvent.getSource().getBindingContext().getPath();
const sProductId = sPath.match(/'([^']+)'/)[1];
this.getOwnerComponent().getRouter().navTo("object", { productId: sProductId });
},
onSearch: function (oEvent) {
const sQuery = oEvent.getParameter("query");
const oBinding = this.byId("productsList").getBinding("items");
const aFilters = sQuery ? [new sap.ui.model.Filter("Name", "Contains", sQuery)] : [];
oBinding.filter(aFilters);
}
});
});
Pattern 2: Worklist XML view (list + search + pull-to-refresh)
worklistTitle}">
Pattern 3: Fiori Elements — annotation-driven LROP
With Fiori Elements you write no controller code. The UI is generated from CDS annotations or manifest.json + metadata.xml.
// Backend annotations (CAP/CDS)
annotate Products with @(
UI.LineItem: [
{ Value: ID },
{ Value: Name },
{ Value: Category },
{ Value: Price }
],
UI.HeaderInfo: {
TypeName: 'Product',
TypeNamePlural: 'Products',
Title: { Value: Name }
},
UI.SelectionFields: [ Category, Price ]
);
The UI5 app's manifest.json references sap.fe.templates.ListReport and the OData service — that's it. The app auto-renders a filter bar, table, and detail page.
Pattern 4: Fragments (reusable UI chunks)
dialogTitle}">
// Controller
onOpenDialog: async function () {
if (!this._oDialog) {
this._oDialog = await this.loadFragment({ name: "my.app.view.Dialog" });
}
this._oDialog.open();
}
Pattern 5: manifest.json essentials
{
"_version": "1.58.0",
"sap.app": {
"id": "my.app",
"type": "application",
"dataSources": {
"mainService": {
"uri": "/sap/opu/odata/sap/ZMY_SERVICE/",
"type": "OData",
"settings": { "odataVersion": "4.0" }
}
}
},
"sap.ui5": {
"dependencies": {
"minUI5Version": "1.120.0",
"libs": { "sap.m": {}, "sap.ui.core": {}, "sap.f": {} }
},
"models": {
"i18n": { "type": "sap.ui.model.resource.ResourceModel",
"settings": { "bundleName": "my.app.i18n.i18n" } },
"": {
"dataSource": "mainService",
"preload": true,
"settings": {
"synchronizationMode": "None",
"operationMode": "Server",
"autoExpandSelect": true,
"earlyRequests": true
}
}
},
"routing": { /* ... see Routing section */ }
}
}
Pattern 6: Custom formatters
// model/formatter.js
sap.ui.define([], function () {
"use strict";
return {
statusText: function (sStatus) {
const oBundle = this.getOwnerComponent().getModel("i18n").getResourceBundle();
switch (sStatus) {
case "A": return oBundle.getText("statusActive");
case "I": return oBundle.getText("statusInactive");
default: return sStatus;
}
},
currencyValue: function (sValue) {
return sValue ? parseFloat(sValue).toFixed(2) : "";
}
};
});
Use in XML:
Error Catalog
| Error | Cause | Fix | |-------|-------|-----| | Cannot read property 'getText' of undefined | i18n model not loaded when controller reads it | Read i18n inside onInit AFTER this.getView().getModel("i18n").getResourceBundle() is available, not at module scope | | The given binding path "/X" is invalid | Path doesn't exist in model or model name is wrong | Use debug tools (Ctrl+Alt+Shift+P) to inspect bindings; check model name prefix (i18n>, device>) | | 404 on /sap/opu/odata/... | Service not activated or wrong path | In SEGW: activate service in /IWFND/MAINT_SERVICE; in RAP: check service binding Publish | | No class sap.m.Xyz | Typo in control name or library not loaded | Check spelling, add the library to sap.ui5.dependencies.libs in manifest.json | | Cannot read properties of null (reading 'getBindingContext') | Control not yet rendered when handler fires | Use onAfterRendering or attachEventOnce instead of onInit | | CSRF token validation failed | Missing X-CSRF-Token on write requests | ODataModel handles this automatically; if using custom fetch, first send GET with X-CSRF-Token: Fetch, reuse token for POST/PUT/DELETE | | sap.ui.model.odata.v4.ODataListBinding: Path does not have a type | autoExpandSelect: true with a path the service doesn't expose | Set autoExpandSelect: false OR make sure every bound property is in the service metadata | | UI5 app works locally but 404 on BTP | destinations not configured in xs-app.json | Add a route pointing to the destination; verify destination exists in BTP cockpit |
Performance Tips
autoExpandSelect: true(OData v4) — UI5 automatically adds$selectand$expandbased on bindings, reducing payload size by 60–80%.growing="true"withgrowingThreshold— never load thousands of rows; let the user scroll to fetch more.- Preload components — set
"preload": trueon critical models in manifest for parallel loading. - Use
sap.ui.table.Tablefor 1000+ rows — it virtualizes rows, unlikesap.m.Table. - Avoid
forceUpdate()— prefer binding refresh over full re-render. - Lazy-load fragments — load dialogs/popovers on first open, not in
onInit. async: truein Component — enable async routing and view loading.- Use
sap-ui-compatVersion=edge— removes legacy code paths. - Minify + preload in build —
ui5 build --clean-dest --allproducesComponent-preload.js(single-file bundle). - Avoid deep
$expand— prefer separate batch requests; deeply nested expands kill response time.
Gotchas
thisbinding in async callbacks — use arrow functions or.bind(this); regular function expressions losethis.- OData v2 vs v4 path syntax — v2:
/Products('HT-1001'); v4:/Products(ID='HT-1001'). They are NOT interchangeable. - Two-way binding requires
changeevents — without them, the model is updated but the server is not. sap.ui.getCore().byId()is deprecated — usethis.byId()inside controllers or view'sbyId().- Fragments share the controller of the view that loaded them — event handlers refer to the view controller, not a separate one.
- Duplicate IDs across fragments — when a fragment is loaded multiple times, prefix IDs with
this.createId()or useaddDependent(). manifest.jsonchanges need a full app reload — hot-reload doesn't pick them up.sap.m.Tablevssap.ui.table.Table— the first is responsive (mobile-first), the second is desktop-only. Pick based on target devices.
Fiori Elements (when to use it)
Use Fiori Elements when:
- The UI is mostly CRUD over a well-modeled entity
- You want consistent Fiori look without custom code
- You can annotate the backend (CAP/CDS) with UI.* annotations
- The template (LROP/OVP/ALP) matches the use case
Avoid Fiori Elements when:
- The UI needs heavy customization (custom visualizations, wizards)
- The data model is flat or non-standard
- You need pixel-perfect design control
Templates:
- LROP (List Report Object Page): filter → table → detail — the most common pattern
- OVP (Overview Page): card-based dashboard
- ALP (Analytical List Page): chart + table analytics
- Worklist: focused task lists
Custom extensions:
- Extension points:
@sap/ux-specificationdefines where you can inject custom fragments/controllers - Flexible programming model:
sap.fe.core.controllerextensions.*for lifecycle hooks
References
references/bindings-cheatsheet.md— Complete binding syntax referencereferences/fiori-elements-annotations.md— UI.* annotation catalog for LROP/OVP/ALPreferences/controls-catalog.md— When to use which UI5 controltemplates/controller-template.js— Copy-paste controller skeletontemplates/xml-view-template.xml— Copy-paste XML view skeletontemplates/manifest-template.json— Copy-paste manifest.json
MCP Server Integration
For live UI5 development assistance, pair this skill with:
@ui5/mcp-server— UI5 control info, API lookup, sample code@sap-ux/fiori-mcp-server— Fiori Tools operations (generate app, build, deploy)@sap/mdk-mcp-server— MDK for mobile-first scenarios
Install via mcp-configs/fullstack-btp.json.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: efeumutaslan
- Source: efeumutaslan/SAP-SKILLS
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.