Sap ui5 fiori
23 SAP development skills for Claude Code — ABAP, RAP, CAP, Fiori, BTP, HANA, S/4HANA, Integration Suite and more. Agent Skills Specification compatible.
npx -y skills add efeumutaslan/SAP-SKILLS --skill sap-ui5-fioriAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
SAPUI5 and Fiori application development skill. Use when building Fiori apps, writing XML views, creating UI5 controllers, binding OData models, configuring manifest.json, implementing routing, using Fiori Elements (LROP/OVP/ALP), or working with SAP Fiori design guidelines. If the user mentions SAPUI5, Fiori, XML view, UI5 controller, manifest, Fiori Elements, sap.m, sap.ui.table, JSONModel, ODataModel, or Fiori launchpad, use this skill.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
14.6 KB, as published. Nobody here has run it
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
<!-- Property binding: single value -->
<Text text="{Name}" />
<!-- Element binding: bind a context (entity) to a control -->
<Form binding="{/Products('HT-1001')}">
<!-- Aggregation binding: list of items -->
<List items="{/Products}">
<StandardListItem title="{Name}" description="{Category}" />
</List>
<!-- Expression binding: computed values -->
<ObjectStatus text="{= ${Price} > 100 ? 'Expensive' : 'Cheap' }" />
<!-- i18n binding -->
<Button text="{i18n>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)
<mvc:View
controllerName="my.app.controller.Worklist"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Page title="{i18n>worklistTitle}">
<subHeader>
<Toolbar>
<SearchField width="100%" search=".onSearch" />
</Toolbar>
</subHeader>
<content>
<List
id="productsList"
items="{path: '/Products', parameters: {$count: true}}"
growing="true"
growingThreshold="20"
growingScrollToLoad="true">
<ObjectListItem
title="{Name}"
number="{
parts: [{path: 'Price'}, {path: 'CurrencyCode'}],
type: 'sap.ui.model.type.Currency',
formatOptions: {showMeasure: false}
}"
numberUnit="{CurrencyCode}"
type="Navigation"
press=".onItemPress">
<attributes>
<ObjectAttribute text="{Category}" />
</attributes>
</ObjectListItem>
</List>
</content>
</Page>
</mvc:View>
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)
<!-- view/Dialog.fragment.xml -->
<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core">
<Dialog title="{i18n>dialogTitle}">
<content>
<Input value="{/newValue}" />
</content>
<beginButton>
<Button text="OK" press=".onDialogOk" />
</beginButton>
<endButton>
<Button text="Cancel" press=".onDialogCancel" />
</endButton>
</Dialog>
</core:FragmentDefinition>
// 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:
<Text text="{path: 'Status', formatter: '.formatter.statusText'}" />
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.